30 Commits

Author SHA1 Message Date
6bbc75fa94 implemented scheduled updates for widgets 2025-03-11 15:50:02 +01:00
b0bbad7f97 added timetable widget for android devices 2025-02-16 18:08:04 +01:00
769fbc1b6a added timetable color substitute teachers 2025-02-14 19:31:46 +01:00
8daf57bcee #75 pinned timetable to german timezone 2025-02-13 22:28:45 +01:00
33d488946a updated android configuration files 2025-02-09 20:42:47 +01:00
41a5e021c5 Merge pull request 'made app modules movable in their order' (#84) from develop-reorderableAppModules into develop
Reviewed-on: #84
Reviewed-by: Pupsi <larslukasneuhaus@gmx.de>
2025-02-09 17:36:20 +00:00
8f58893553 Merge branch 'develop' into develop-reorderableAppModules 2025-02-09 17:36:12 +00:00
626d3d5564 added minimum message drag distance for chat reply 2025-02-09 15:16:02 +01:00
d833cdb733 made app modules movable in their order 2025-02-09 15:06:14 +01:00
8868914a76 restructured settings and devtools 2025-02-08 23:21:20 +01:00
70e6f82b10 updated chat images loading animation 2025-02-08 22:53:14 +01:00
6651613331 fixed files cache not working correctly 2025-02-08 22:35:20 +01:00
9ad0f624de Merge remote-tracking branch 'origin/develop' into develop 2025-02-08 21:40:49 +01:00
82c143f847 bumped version 2025-02-08 21:40:39 +01:00
1fdf731b81 Merge pull request 'added option for timetable naming modes' (#83) from feature-timetableNamingSetting into develop
Reviewed-on: #83
Reviewed-by: Pupsi <larslukasneuhaus@gmx.de>
2025-01-24 22:27:06 +00:00
2d3ccd25b4 Merge branch 'develop' into feature-timetableNamingSetting 2025-01-24 22:26:52 +00:00
385ee806d6 updated feedback info text 2025-01-24 13:53:12 +01:00
92aef41031 updated room plan image 2025-01-24 11:54:35 +01:00
65b29ec4b8 added option for timetable naming modes 2025-01-24 11:50:14 +01:00
9f51d68531 updated build runner tasks 2025-01-24 11:02:03 +01:00
5bc4ba6332 replaced version wildcard with version range 2025-01-23 23:39:01 +01:00
e9739ac2d5 updated app badger 2025-01-23 22:41:22 +01:00
4d3a33dd9b updated project 2025-01-23 11:20:08 +01:00
ddeeaeaeac Merge pull request 'develop-bloc-holidays' (#72) from develop-bloc-holidays into develop
Reviewed-on: #72
Reviewed-by: Pupsi <larslukasneuhaus@gmx.de>
2024-06-23 21:00:45 +00:00
c8e31b896b added eof linebreak 2024-06-23 22:59:26 +02:00
c443a1d567 fixed disclaimer not showing on first visit 2024-06-23 20:31:43 +02:00
08ef784f57 Merge remote-tracking branch 'origin/develop' into develop-bloc-holidays 2024-06-22 16:47:15 +02:00
fe93a94fc6 bloc for holidays 2024-06-12 15:53:13 +02:00
a33c4ddac5 wip: fixed state not updating correctly 2024-05-27 22:28:42 +02:00
634fe41e78 wip: bloc for holidays 2024-05-14 14:54:01 +02:00
118 changed files with 2546 additions and 1154 deletions

View File

@@ -25,9 +25,10 @@ if (flutterVersionName == null) {
android { android {
namespace "eu.mhsl.marianum.mobile.client" namespace "eu.mhsl.marianum.mobile.client"
compileSdk flutter.compileSdkVersion compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion ndkVersion "27.0.12077973"
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@@ -41,11 +42,8 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "eu.mhsl.marianum.mobile.client" applicationId "eu.mhsl.marianum.mobile.client"
// You can update the following values to match your application needs. minSdkVersion 26
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@@ -59,6 +57,9 @@ android {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
buildFeatures {
viewBinding true
}
} }
flutter { flutter {
@@ -66,5 +67,6 @@ flutter {
} }
dependencies { dependencies {
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:2.0.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
} }

0
android/app/proguard-rules.pro vendored Normal file
View File

View File

@@ -1,45 +1,73 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<!--
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.
-->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="Marianum Fulda" tools:replace="android:label"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:label="Marianum Fulda">
<receiver
android:name=".TimetableWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/timetable_widget_info" />
</receiver>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true" android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
<!--
Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI.
-->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme" />
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!--
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> Don't delete the meta-data below.
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> </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. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@@ -0,0 +1,39 @@
package eu.mhsl.marianum.mobile.client
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetPlugin
import android.util.Base64
/**
* Implementation of App Widget functionality.
*/
class TimetableWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.timetable_widget).apply {
val imageBase64 = widgetData.getString("screen", null) ?: return@apply
val imageBytes = Base64.decode(imageBase64, Base64.DEFAULT);
val imageBitmap: Bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
setImageViewBitmap(R.id.widget_image, imageBitmap)
}
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
val pendingIntent = PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.background, pendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!--
Background for widgets to make the rounded corners based on the
appWidgetRadius attribute value
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="?attr/appWidgetRadius" />
<solid android:color="?android:attr/colorBackground" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!--
Background for views inside widgets to make the rounded corners based on the
appWidgetInnerRadius attribute value
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="?attr/appWidgetInnerRadius" />
<solid android:color="?android:attr/colorAccent" />
</shape>

View File

@@ -0,0 +1,26 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/background"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:padding="0dp"
android:theme="@style/Theme.Android.AppWidgetContainer">
<ImageView
android:id="@+id/widget_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:layout_marginBottom="0dp"
android:layout_weight="1"
android:adjustViewBounds="false"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:src="@drawable/timetable_widget_default"
android:visibility="visible"
tools:visibility="visible" />
</RelativeLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Having themes.xml for night-v31 because of the priority order of the resource qualifiers.
-->
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>
</resources>

View File

@@ -0,0 +1,14 @@
<resources>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_background</item>
</style>
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_inner_view_background</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
</resources>

View File

@@ -18,4 +18,18 @@
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_background</item>
<item name="android:clipToOutline">true</item>
</style>
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:padding">?attr/appWidgetPadding</item>
<item name="android:background">@drawable/app_widget_inner_view_background</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:clipToOutline">true</item>
</style>
</resources> </resources>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Having themes.xml for v31 variant because @android:dimen/system_app_widget_background_radius
and @android:dimen/system_app_widget_internal_padding requires API level 31
-->
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item>
<item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<resources>
<declare-styleable name="AppWidgetAttrs">
<attr name="appWidgetPadding" format="dimension" />
<attr name="appWidgetInnerRadius" format="dimension" />
<attr name="appWidgetRadius" format="dimension" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,6 @@
<resources>
<color name="light_blue_50">#FFE1F5FE</color>
<color name="light_blue_200">#FF81D4FA</color>
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
Refer to App Widget Documentation for margin information
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
-->
<dimen name="widget_margin">0dp</dimen>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="appwidget_text">Marianum Vertretungsplan</string>
<string name="add_widget">Hinzufügen</string>
<string name="app_widget_description">Übersicht zum Vertretungsplan</string>
</resources>

View File

@@ -19,4 +19,14 @@
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
<item name="android:id">@android:id/background</item>
<item name="android:background">?android:attr/colorBackground</item>
</style>
<style name="Widget.Android.AppWidget.InnerView" parent="android:Widget">
<item name="android:background">?android:attr/colorBackground</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
</resources> </resources>

View File

@@ -0,0 +1,17 @@
<resources>
<style name="Theme.Android.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
<!-- Radius of the outer bound of widgets to make the rounded corners -->
<item name="appWidgetRadius">16dp</item>
<!--
Radius of the inner view's bound of widgets to make the rounded corners.
It needs to be 8dp or less than the value of appWidgetRadius
-->
<item name="appWidgetInnerRadius">8dp</item>
</style>
<style name="Theme.Android.AppWidgetContainer" parent="Theme.Android.AppWidgetContainerParent">
<!-- Apply padding to avoid the content of the widget colliding with the rounded corners -->
<item name="appWidgetPadding">16dp</item>
</style>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description"
android:initialKeyguardLayout="@layout/timetable_widget"
android:initialLayout="@layout/timetable_widget"
android:minWidth="220dp"
android:minHeight="294dp"
android:minResizeWidth="110dp"
android:minResizeHeight="147dp"
android:previewImage="@drawable/timetable_widget_preview"
android:previewLayout="@layout/timetable_widget"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="3"
android:targetCellHeight="4"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen" />

View File

@@ -2,6 +2,21 @@ allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
// [required] background_fetch
maven {
url "${project(':background_fetch').projectDir}/libs"
}
}
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10'
} }
} }

View File

@@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -19,7 +19,7 @@ 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 "7.3.0" apply false id "com.android.application" version '8.7.3' apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false id "org.jetbrains.kotlin.android" version "1.8.10" apply false
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -5,5 +5,5 @@ abstract class ApiResponse {
late http.Response rawResponse; late http.Response rawResponse;
@JsonKey(includeIfNull: false) @JsonKey(includeIfNull: false)
late Map<String, String>? headers; Map<String, String>? headers;
} }

View File

@@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'getHolidaysResponse.dart'; import 'getHolidaysResponse.dart';
@@ -7,11 +6,10 @@ import 'getHolidaysResponse.dart';
class GetHolidays { class GetHolidays {
Future<GetHolidaysResponse> query() async { Future<GetHolidaysResponse> query() async {
var response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body; var response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body;
var data = jsonDecode(response) as List<dynamic>;
return GetHolidaysResponse( return GetHolidaysResponse(
List<GetHolidaysResponseObject>.from( List<GetHolidaysResponseObject>.from(
jsonDecode(response).map<GetHolidaysResponseObject>( data.map<GetHolidaysResponseObject>((e) => GetHolidaysResponseObject.fromJson(e as Map<String, dynamic>))
GetHolidaysResponseObject.fromJson
)
) )
); );
} }

View File

@@ -15,8 +15,7 @@ class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
return GetHolidaysResponse( return GetHolidaysResponse(
List<GetHolidaysResponseObject>.from( List<GetHolidaysResponseObject>.from(
parsedListJson.map<GetHolidaysResponseObject>( parsedListJson.map<GetHolidaysResponseObject>(
// ignore: unnecessary_lambdas (i) => GetHolidaysResponseObject.fromJson(i as Map<String, dynamic>)
(dynamic i) => GetHolidaysResponseObject.fromJson(i)
) )
) )
); );

View File

@@ -16,26 +16,19 @@ GetHolidaysResponse _$GetHolidaysResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetHolidaysResponseToJson(GetHolidaysResponse instance) { Map<String, dynamic> _$GetHolidaysResponseToJson(
final val = <String, dynamic>{}; GetHolidaysResponse instance) =>
<String, dynamic>{
void writeNotNull(String key, dynamic value) { if (instance.headers case final value?) 'headers': value,
if (value != null) { 'data': instance.data.map((e) => e.toJson()).toList(),
val[key] = value; };
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson( GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetHolidaysResponseObject( GetHolidaysResponseObject(
start: json['start'] as String, start: json['start'] as String,
end: json['end'] as String, end: json['end'] as String,
year: json['year'] as int, year: (json['year'] as num).toInt(),
stateCode: json['stateCode'] as String, stateCode: json['stateCode'] as String,
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String, slug: json['slug'] as String,

View File

@@ -9,7 +9,7 @@ part of 'fileSharingApiParams.dart';
FileSharingApiParams _$FileSharingApiParamsFromJson( FileSharingApiParams _$FileSharingApiParamsFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
FileSharingApiParams( FileSharingApiParams(
shareType: json['shareType'] as int, shareType: (json['shareType'] as num).toInt(),
shareWith: json['shareWith'] as String, shareWith: json['shareWith'] as String,
path: json['path'] as String, path: json['path'] as String,
referenceId: json['referenceId'] as String?, referenceId: json['referenceId'] as String?,

View File

@@ -10,38 +10,33 @@ GetChatParams _$GetChatParamsFromJson(Map<String, dynamic> json) =>
GetChatParams( GetChatParams(
lookIntoFuture: lookIntoFuture:
$enumDecode(_$GetChatParamsSwitchEnumMap, json['lookIntoFuture']), $enumDecode(_$GetChatParamsSwitchEnumMap, json['lookIntoFuture']),
limit: json['limit'] as int?, limit: (json['limit'] as num?)?.toInt(),
lastKnownMessageId: json['lastKnownMessageId'] as int?, lastKnownMessageId: (json['lastKnownMessageId'] as num?)?.toInt(),
lastCommonReadId: json['lastCommonReadId'] as int?, lastCommonReadId: (json['lastCommonReadId'] as num?)?.toInt(),
timeout: json['timeout'] as int?, timeout: (json['timeout'] as num?)?.toInt(),
setReadMarker: $enumDecodeNullable( setReadMarker: $enumDecodeNullable(
_$GetChatParamsSwitchEnumMap, json['setReadMarker']), _$GetChatParamsSwitchEnumMap, json['setReadMarker']),
includeLastKnown: $enumDecodeNullable( includeLastKnown: $enumDecodeNullable(
_$GetChatParamsSwitchEnumMap, json['includeLastKnown']), _$GetChatParamsSwitchEnumMap, json['includeLastKnown']),
); );
Map<String, dynamic> _$GetChatParamsToJson(GetChatParams instance) { Map<String, dynamic> _$GetChatParamsToJson(GetChatParams instance) =>
final val = <String, dynamic>{ <String, dynamic>{
'lookIntoFuture': _$GetChatParamsSwitchEnumMap[instance.lookIntoFuture]!, 'lookIntoFuture': _$GetChatParamsSwitchEnumMap[instance.lookIntoFuture]!,
if (instance.limit case final value?) 'limit': value,
if (instance.lastKnownMessageId case final value?)
'lastKnownMessageId': value,
if (instance.lastCommonReadId case final value?)
'lastCommonReadId': value,
if (instance.timeout case final value?) 'timeout': value,
if (_$GetChatParamsSwitchEnumMap[instance.setReadMarker]
case final value?)
'setReadMarker': value,
if (_$GetChatParamsSwitchEnumMap[instance.includeLastKnown]
case final value?)
'includeLastKnown': value,
}; };
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('limit', instance.limit);
writeNotNull('lastKnownMessageId', instance.lastKnownMessageId);
writeNotNull('lastCommonReadId', instance.lastCommonReadId);
writeNotNull('timeout', instance.timeout);
writeNotNull(
'setReadMarker', _$GetChatParamsSwitchEnumMap[instance.setReadMarker]);
writeNotNull('includeLastKnown',
_$GetChatParamsSwitchEnumMap[instance.includeLastKnown]);
return val;
}
const _$GetChatParamsSwitchEnumMap = { const _$GetChatParamsSwitchEnumMap = {
GetChatParamsSwitch.on: 1, GetChatParamsSwitch.on: 1,
GetChatParamsSwitch.off: 0, GetChatParamsSwitch.off: 0,

View File

@@ -15,30 +15,22 @@ GetChatResponse _$GetChatResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) { Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'data': instance.data.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetChatResponseObject _$GetChatResponseObjectFromJson( GetChatResponseObject _$GetChatResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetChatResponseObject( GetChatResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['token'] as String, json['token'] as String,
$enumDecode( $enumDecode(
_$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']), _$GetRoomResponseObjectMessageActorTypeEnumMap, json['actorType']),
json['actorId'] as String, json['actorId'] as String,
json['actorDisplayName'] as String, json['actorDisplayName'] as String,
json['timestamp'] as int, (json['timestamp'] as num).toInt(),
json['systemMessage'] as String, json['systemMessage'] as String,
$enumDecode( $enumDecode(
_$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']), _$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']),
@@ -47,7 +39,7 @@ GetChatResponseObject _$GetChatResponseObjectFromJson(
json['message'] as String, json['message'] as String,
_fromJson(json['messageParameters']), _fromJson(json['messageParameters']),
(json['reactions'] as Map<String, dynamic>?)?.map( (json['reactions'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as int), (k, e) => MapEntry(k, (e as num).toInt()),
), ),
(json['reactionsSelf'] as List<dynamic>?) (json['reactionsSelf'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)

View File

@@ -8,7 +8,7 @@ part of 'createRoomParams.dart';
CreateRoomParams _$CreateRoomParamsFromJson(Map<String, dynamic> json) => CreateRoomParams _$CreateRoomParamsFromJson(Map<String, dynamic> json) =>
CreateRoomParams( CreateRoomParams(
roomType: json['roomType'] as int, roomType: (json['roomType'] as num).toInt(),
invite: json['invite'] as String, invite: json['invite'] as String,
source: json['source'] as String?, source: json['source'] as String?,
roomName: json['roomName'] as String?, roomName: json['roomName'] as String?,

View File

@@ -18,34 +18,26 @@ GetParticipantsResponse _$GetParticipantsResponseFromJson(
); );
Map<String, dynamic> _$GetParticipantsResponseToJson( Map<String, dynamic> _$GetParticipantsResponseToJson(
GetParticipantsResponse instance) { GetParticipantsResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'data': instance.data.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson( GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetParticipantsResponseObject( GetParticipantsResponseObject(
json['attendeeId'] as int, (json['attendeeId'] as num).toInt(),
json['actorType'] as String, json['actorType'] as String,
json['actorId'] as String, json['actorId'] as String,
json['displayName'] as String, json['displayName'] as String,
$enumDecode(_$GetParticipantsResponseObjectParticipantTypeEnumMap, $enumDecode(_$GetParticipantsResponseObjectParticipantTypeEnumMap,
json['participantType']), json['participantType']),
json['lastPing'] as int, (json['lastPing'] as num).toInt(),
$enumDecode(_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap, $enumDecode(_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap,
json['inCall']), json['inCall']),
json['permissions'] as int, (json['permissions'] as num).toInt(),
json['attendeePermissions'] as int, (json['attendeePermissions'] as num).toInt(),
json['sessionId'] as String?, json['sessionId'] as String?,
(json['sessionIds'] as List<dynamic>).map((e) => e as String).toList(), (json['sessionIds'] as List<dynamic>).map((e) => e as String).toList(),
json['status'] as String?, json['status'] as String?,

View File

@@ -22,20 +22,12 @@ GetReactionsResponse _$GetReactionsResponseFromJson(
); );
Map<String, dynamic> _$GetReactionsResponseToJson( Map<String, dynamic> _$GetReactionsResponseToJson(
GetReactionsResponse instance) { GetReactionsResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'data': instance.data
if (value != null) { .map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList())),
val[key] = value; };
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data
.map((k, e) => MapEntry(k, e.map((e) => e.toJson()).toList()));
return val;
}
GetReactionsResponseObject _$GetReactionsResponseObjectFromJson( GetReactionsResponseObject _$GetReactionsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
@@ -44,7 +36,7 @@ GetReactionsResponseObject _$GetReactionsResponseObjectFromJson(
_$GetReactionsResponseObjectActorTypeEnumMap, json['actorType']), _$GetReactionsResponseObjectActorTypeEnumMap, json['actorType']),
json['actorId'] as String, json['actorId'] as String,
json['actorDisplayName'] as String, json['actorDisplayName'] as String,
json['timestamp'] as int, (json['timestamp'] as num).toInt(),
); );
Map<String, dynamic> _$GetReactionsResponseObjectToJson( Map<String, dynamic> _$GetReactionsResponseObjectToJson(

View File

@@ -11,7 +11,7 @@ GetRoomParams _$GetRoomParamsFromJson(Map<String, dynamic> json) =>
noStatusUpdate: $enumDecodeNullable( noStatusUpdate: $enumDecodeNullable(
_$GetRoomParamsStatusUpdateEnumMap, json['noStatusUpdate']), _$GetRoomParamsStatusUpdateEnumMap, json['noStatusUpdate']),
includeStatus: json['includeStatus'] as bool?, includeStatus: json['includeStatus'] as bool?,
modifiedSince: json['modifiedSince'] as int?, modifiedSince: (json['modifiedSince'] as num?)?.toInt(),
); );
Map<String, dynamic> _$GetRoomParamsToJson(GetRoomParams instance) => Map<String, dynamic> _$GetRoomParamsToJson(GetRoomParams instance) =>

View File

@@ -15,50 +15,42 @@ GetRoomResponse _$GetRoomResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) { Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'data': instance.data.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['data'] = instance.data.map((e) => e.toJson()).toList();
return val;
}
GetRoomResponseObject _$GetRoomResponseObjectFromJson( GetRoomResponseObject _$GetRoomResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetRoomResponseObject( GetRoomResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['token'] as String, json['token'] as String,
$enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']), $enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']),
json['name'] as String, json['name'] as String,
json['displayName'] as String, json['displayName'] as String,
json['description'] as String, json['description'] as String,
json['participantType'] as int, (json['participantType'] as num).toInt(),
json['participantFlags'] as int, (json['participantFlags'] as num).toInt(),
json['readOnly'] as int, (json['readOnly'] as num).toInt(),
json['listable'] as int, (json['listable'] as num).toInt(),
json['lastPing'] as int, (json['lastPing'] as num).toInt(),
json['sessionId'] as String, json['sessionId'] as String,
json['hasPassword'] as bool, json['hasPassword'] as bool,
json['hasCall'] as bool, json['hasCall'] as bool,
json['callFlag'] as int, (json['callFlag'] as num).toInt(),
json['canStartCall'] as bool, json['canStartCall'] as bool,
json['canDeleteConversation'] as bool, json['canDeleteConversation'] as bool,
json['canLeaveConversation'] as bool, json['canLeaveConversation'] as bool,
json['lastActivity'] as int, (json['lastActivity'] as num).toInt(),
json['isFavorite'] as bool, json['isFavorite'] as bool,
$enumDecode(_$GetRoomResponseObjectParticipantNotificationLevelEnumMap, $enumDecode(_$GetRoomResponseObjectParticipantNotificationLevelEnumMap,
json['notificationLevel']), json['notificationLevel']),
json['unreadMessages'] as int, (json['unreadMessages'] as num).toInt(),
json['unreadMention'] as bool, json['unreadMention'] as bool,
json['unreadMentionDirect'] as bool, json['unreadMentionDirect'] as bool,
json['lastReadMessage'] as int, (json['lastReadMessage'] as num).toInt(),
json['lastCommonReadMessage'] as int, (json['lastCommonReadMessage'] as num).toInt(),
GetChatResponseObject.fromJson( GetChatResponseObject.fromJson(
json['lastMessage'] as Map<String, dynamic>), json['lastMessage'] as Map<String, dynamic>),
json['status'] as String?, json['status'] as String?,

View File

@@ -12,17 +12,8 @@ SendMessageParams _$SendMessageParamsFromJson(Map<String, dynamic> json) =>
replyTo: json['replyTo'] as String?, replyTo: json['replyTo'] as String?,
); );
Map<String, dynamic> _$SendMessageParamsToJson(SendMessageParams instance) { Map<String, dynamic> _$SendMessageParamsToJson(SendMessageParams instance) =>
final val = <String, dynamic>{ <String, dynamic>{
'message': instance.message, 'message': instance.message,
if (instance.replyTo case final value?) 'replyTo': value,
}; };
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('replyTo', instance.replyTo);
return val;
}

View File

@@ -8,7 +8,7 @@ part of 'setReadMarkerParams.dart';
SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) => SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) =>
SetReadMarkerParams( SetReadMarkerParams(
lastReadMessage: json['lastReadMessage'] as int?, lastReadMessage: (json['lastReadMessage'] as num?)?.toInt(),
); );
Map<String, dynamic> _$SetReadMarkerParamsToJson( Map<String, dynamic> _$SetReadMarkerParamsToJson(

View File

@@ -12,7 +12,7 @@ CacheableFile _$CacheableFileFromJson(Map<String, dynamic> json) =>
isDirectory: json['isDirectory'] as bool, isDirectory: json['isDirectory'] as bool,
name: json['name'] as String, name: json['name'] as String,
mimeType: json['mimeType'] as String?, mimeType: json['mimeType'] as String?,
size: json['size'] as int?, size: (json['size'] as num?)?.toInt(),
eTag: json['eTag'] as String?, eTag: json['eTag'] as String?,
createdAt: json['createdAt'] == null createdAt: json['createdAt'] == null
? null ? null

View File

@@ -15,16 +15,8 @@ ListFilesResponse _$ListFilesResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$ListFilesResponseToJson(ListFilesResponse instance) { Map<String, dynamic> _$ListFilesResponseToJson(ListFilesResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'files': instance.files.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['files'] = instance.files.map((e) => e.toJson()).toList();
return val;
}

View File

@@ -17,20 +17,13 @@ GetBreakersResponse _$GetBreakersResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetBreakersResponseToJson(GetBreakersResponse instance) { Map<String, dynamic> _$GetBreakersResponseToJson(
final val = <String, dynamic>{}; GetBreakersResponse instance) =>
<String, dynamic>{
void writeNotNull(String key, dynamic value) { if (instance.headers case final value?) 'headers': value,
if (value != null) { 'global': instance.global.toJson(),
val[key] = value; 'regional': instance.regional.map((k, e) => MapEntry(k, e.toJson())),
} };
}
writeNotNull('headers', instance.headers);
val['global'] = instance.global.toJson();
val['regional'] = instance.regional.map((k, e) => MapEntry(k, e.toJson()));
return val;
}
GetBreakersReponseObject _$GetBreakersReponseObjectFromJson( GetBreakersReponseObject _$GetBreakersReponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>

View File

@@ -17,16 +17,8 @@ GetCustomTimetableEventResponse _$GetCustomTimetableEventResponseFromJson(
); );
Map<String, dynamic> _$GetCustomTimetableEventResponseToJson( Map<String, dynamic> _$GetCustomTimetableEventResponseToJson(
GetCustomTimetableEventResponse instance) { GetCustomTimetableEventResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'events': instance.events,
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['events'] = instance.events;
return val;
}

View File

@@ -11,7 +11,7 @@ AddFeedbackParams _$AddFeedbackParamsFromJson(Map<String, dynamic> json) =>
user: json['user'] as String, user: json['user'] as String,
feedback: json['feedback'] as String, feedback: json['feedback'] as String,
screenshot: json['screenshot'] as String?, screenshot: json['screenshot'] as String?,
appVersion: json['appVersion'] as int, appVersion: (json['appVersion'] as num).toInt(),
); );
Map<String, dynamic> _$AddFeedbackParamsToJson(AddFeedbackParams instance) => Map<String, dynamic> _$AddFeedbackParamsToJson(AddFeedbackParams instance) =>

View File

@@ -12,7 +12,7 @@ UpdateUserIndexParams _$UpdateUserIndexParamsFromJson(
user: json['user'] as String, user: json['user'] as String,
username: json['username'] as String, username: json['username'] as String,
device: json['device'] as String, device: json['device'] as String,
appVersion: json['appVersion'] as int, appVersion: (json['appVersion'] as num).toInt(),
deviceInfo: json['deviceInfo'] as String, deviceInfo: json['deviceInfo'] as String,
); );

View File

@@ -4,7 +4,7 @@ import 'dart:developer';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:package_info/package_info.dart'; import 'package:package_info_plus/package_info_plus.dart';
import '../../../../../model/accountData.dart'; import '../../../../../model/accountData.dart';
import '../../../mhslApi.dart'; import '../../../mhslApi.dart';

View File

@@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:localstore/localstore.dart'; import 'package:localstore/localstore.dart';
import 'apiResponse.dart'; import 'apiResponse.dart';
import 'webuntis/webuntisError.dart';
abstract class RequestCache<T extends ApiResponse?> { abstract class RequestCache<T extends ApiResponse?> {
static const int cacheNothing = 0; static const int cacheNothing = 0;
@@ -40,7 +39,7 @@ abstract class RequestCache<T extends ApiResponse?> {
'json': jsonEncode(newValue), 'json': jsonEncode(newValue),
'lastupdate': DateTime.now().millisecondsSinceEpoch 'lastupdate': DateTime.now().millisecondsSinceEpoch
}); });
} on WebuntisError catch(e) { } on Exception catch(e) {
onError(e); onError(e);
} }
} }

View File

@@ -10,27 +10,19 @@ AuthenticateResponse _$AuthenticateResponseFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
AuthenticateResponse( AuthenticateResponse(
json['sessionId'] as String, json['sessionId'] as String,
json['personType'] as int, (json['personType'] as num).toInt(),
json['personId'] as int, (json['personId'] as num).toInt(),
json['klasseId'] as int, (json['klasseId'] as num).toInt(),
)..headers = (json['headers'] as Map<String, dynamic>?)?.map( )..headers = (json['headers'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$AuthenticateResponseToJson( Map<String, dynamic> _$AuthenticateResponseToJson(
AuthenticateResponse instance) { AuthenticateResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'sessionId': instance.sessionId,
if (value != null) { 'personType': instance.personType,
val[key] = value; 'personId': instance.personId,
} 'klasseId': instance.klasseId,
} };
writeNotNull('headers', instance.headers);
val['sessionId'] = instance.sessionId;
val['personType'] = instance.personType;
val['personId'] = instance.personId;
val['klasseId'] = instance.klasseId;
return val;
}

View File

@@ -16,28 +16,21 @@ GetHolidaysResponse _$GetHolidaysResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetHolidaysResponseToJson(GetHolidaysResponse instance) { Map<String, dynamic> _$GetHolidaysResponseToJson(
final val = <String, dynamic>{}; GetHolidaysResponse instance) =>
<String, dynamic>{
void writeNotNull(String key, dynamic value) { if (instance.headers case final value?) 'headers': value,
if (value != null) { 'result': instance.result.map((e) => e.toJson()).toList(),
val[key] = value; };
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson( GetHolidaysResponseObject _$GetHolidaysResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetHolidaysResponseObject( GetHolidaysResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['startDate'] as int, (json['startDate'] as num).toInt(),
json['endDate'] as int, (json['endDate'] as num).toInt(),
); );
Map<String, dynamic> _$GetHolidaysResponseObjectToJson( Map<String, dynamic> _$GetHolidaysResponseObjectToJson(

View File

@@ -16,24 +16,16 @@ GetRoomsResponse _$GetRoomsResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetRoomsResponseToJson(GetRoomsResponse instance) { Map<String, dynamic> _$GetRoomsResponseToJson(GetRoomsResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'result': instance.result.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetRoomsResponseObject _$GetRoomsResponseObjectFromJson( GetRoomsResponseObject _$GetRoomsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetRoomsResponseObject( GetRoomsResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['active'] as bool, json['active'] as bool,

View File

@@ -16,24 +16,17 @@ GetSubjectsResponse _$GetSubjectsResponseFromJson(Map<String, dynamic> json) =>
(k, e) => MapEntry(k, e as String), (k, e) => MapEntry(k, e as String),
); );
Map<String, dynamic> _$GetSubjectsResponseToJson(GetSubjectsResponse instance) { Map<String, dynamic> _$GetSubjectsResponseToJson(
final val = <String, dynamic>{}; GetSubjectsResponse instance) =>
<String, dynamic>{
void writeNotNull(String key, dynamic value) { if (instance.headers case final value?) 'headers': value,
if (value != null) { 'result': instance.result.map((e) => e.toJson()).toList(),
val[key] = value; };
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetSubjectsResponseObject _$GetSubjectsResponseObjectFromJson( GetSubjectsResponseObject _$GetSubjectsResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetSubjectsResponseObject( GetSubjectsResponseObject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longName'] as String, json['longName'] as String,
json['alternateName'] as String, json['alternateName'] as String,

View File

@@ -22,8 +22,8 @@ GetTimetableParamsOptions _$GetTimetableParamsOptionsFromJson(
GetTimetableParamsOptions( GetTimetableParamsOptions(
element: GetTimetableParamsOptionsElement.fromJson( element: GetTimetableParamsOptionsElement.fromJson(
json['element'] as Map<String, dynamic>), json['element'] as Map<String, dynamic>),
startDate: json['startDate'] as int?, startDate: (json['startDate'] as num?)?.toInt(),
endDate: json['endDate'] as int?, endDate: (json['endDate'] as num?)?.toInt(),
onlyBaseTimetable: json['onlyBaseTimetable'] as bool?, onlyBaseTimetable: json['onlyBaseTimetable'] as bool?,
showBooking: json['showBooking'] as bool?, showBooking: json['showBooking'] as bool?,
showInfo: json['showInfo'] as bool?, showInfo: json['showInfo'] as bool?,
@@ -46,49 +46,42 @@ GetTimetableParamsOptions _$GetTimetableParamsOptionsFromJson(
); );
Map<String, dynamic> _$GetTimetableParamsOptionsToJson( Map<String, dynamic> _$GetTimetableParamsOptionsToJson(
GetTimetableParamsOptions instance) { GetTimetableParamsOptions instance) =>
final val = <String, dynamic>{ <String, dynamic>{
'element': instance.element.toJson(), 'element': instance.element.toJson(),
if (instance.startDate case final value?) 'startDate': value,
if (instance.endDate case final value?) 'endDate': value,
if (instance.onlyBaseTimetable case final value?)
'onlyBaseTimetable': value,
if (instance.showBooking case final value?) 'showBooking': value,
if (instance.showInfo case final value?) 'showInfo': value,
if (instance.showSubstText case final value?) 'showSubstText': value,
if (instance.showLsText case final value?) 'showLsText': value,
if (instance.showLsNumber case final value?) 'showLsNumber': value,
if (instance.showStudentgroup case final value?)
'showStudentgroup': value,
if (instance.klasseFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList()
case final value?)
'klasseFields': value,
if (instance.roomFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList()
case final value?)
'roomFields': value,
if (instance.subjectFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList()
case final value?)
'subjectFields': value,
if (instance.teacherFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList()
case final value?)
'teacherFields': value,
}; };
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('startDate', instance.startDate);
writeNotNull('endDate', instance.endDate);
writeNotNull('onlyBaseTimetable', instance.onlyBaseTimetable);
writeNotNull('showBooking', instance.showBooking);
writeNotNull('showInfo', instance.showInfo);
writeNotNull('showSubstText', instance.showSubstText);
writeNotNull('showLsText', instance.showLsText);
writeNotNull('showLsNumber', instance.showLsNumber);
writeNotNull('showStudentgroup', instance.showStudentgroup);
writeNotNull(
'klasseFields',
instance.klasseFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList());
writeNotNull(
'roomFields',
instance.roomFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList());
writeNotNull(
'subjectFields',
instance.subjectFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList());
writeNotNull(
'teacherFields',
instance.teacherFields
?.map((e) => _$GetTimetableParamsOptionsFieldsEnumMap[e]!)
.toList());
return val;
}
const _$GetTimetableParamsOptionsFieldsEnumMap = { const _$GetTimetableParamsOptionsFieldsEnumMap = {
GetTimetableParamsOptionsFields.id: 'id', GetTimetableParamsOptionsFields.id: 'id',
GetTimetableParamsOptionsFields.name: 'name', GetTimetableParamsOptionsFields.name: 'name',
@@ -99,30 +92,22 @@ const _$GetTimetableParamsOptionsFieldsEnumMap = {
GetTimetableParamsOptionsElement _$GetTimetableParamsOptionsElementFromJson( GetTimetableParamsOptionsElement _$GetTimetableParamsOptionsElementFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableParamsOptionsElement( GetTimetableParamsOptionsElement(
id: json['id'] as int, id: (json['id'] as num).toInt(),
type: json['type'] as int, type: (json['type'] as num).toInt(),
keyType: $enumDecodeNullable( keyType: $enumDecodeNullable(
_$GetTimetableParamsOptionsElementKeyTypeEnumMap, json['keyType']), _$GetTimetableParamsOptionsElementKeyTypeEnumMap, json['keyType']),
); );
Map<String, dynamic> _$GetTimetableParamsOptionsElementToJson( Map<String, dynamic> _$GetTimetableParamsOptionsElementToJson(
GetTimetableParamsOptionsElement instance) { GetTimetableParamsOptionsElement instance) =>
final val = <String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
'type': instance.type, 'type': instance.type,
if (_$GetTimetableParamsOptionsElementKeyTypeEnumMap[instance.keyType]
case final value?)
'keyType': value,
}; };
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('keyType',
_$GetTimetableParamsOptionsElementKeyTypeEnumMap[instance.keyType]);
return val;
}
const _$GetTimetableParamsOptionsElementKeyTypeEnumMap = { const _$GetTimetableParamsOptionsElementKeyTypeEnumMap = {
GetTimetableParamsOptionsElementKeyType.id: 'id', GetTimetableParamsOptionsElementKeyType.id: 'id',
GetTimetableParamsOptionsElementKeyType.name: 'name', GetTimetableParamsOptionsElementKeyType.name: 'name',

View File

@@ -18,33 +18,25 @@ GetTimetableResponse _$GetTimetableResponseFromJson(
); );
Map<String, dynamic> _$GetTimetableResponseToJson( Map<String, dynamic> _$GetTimetableResponseToJson(
GetTimetableResponse instance) { GetTimetableResponse instance) =>
final val = <String, dynamic>{}; <String, dynamic>{
if (instance.headers case final value?) 'headers': value,
void writeNotNull(String key, dynamic value) { 'result': instance.result.map((e) => e.toJson()).toList(),
if (value != null) { };
val[key] = value;
}
}
writeNotNull('headers', instance.headers);
val['result'] = instance.result.map((e) => e.toJson()).toList();
return val;
}
GetTimetableResponseObject _$GetTimetableResponseObjectFromJson( GetTimetableResponseObject _$GetTimetableResponseObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObject( GetTimetableResponseObject(
id: json['id'] as int, id: (json['id'] as num).toInt(),
date: json['date'] as int, date: (json['date'] as num).toInt(),
startTime: json['startTime'] as int, startTime: (json['startTime'] as num).toInt(),
endTime: json['endTime'] as int, endTime: (json['endTime'] as num).toInt(),
lstype: json['lstype'] as String?, lstype: json['lstype'] as String?,
code: json['code'] as String?, code: json['code'] as String?,
info: json['info'] as String?, info: json['info'] as String?,
substText: json['substText'] as String?, substText: json['substText'] as String?,
lstext: json['lstext'] as String?, lstext: json['lstext'] as String?,
lsnumber: json['lsnumber'] as int?, lsnumber: (json['lsnumber'] as num?)?.toInt(),
statflags: json['statflags'] as String?, statflags: json['statflags'] as String?,
activityType: json['activityType'] as String?, activityType: json['activityType'] as String?,
sg: json['sg'] as String?, sg: json['sg'] as String?,
@@ -110,7 +102,7 @@ GetTimetableResponseObjectFieldsObject
_$GetTimetableResponseObjectFieldsObjectFromJson( _$GetTimetableResponseObjectFieldsObjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectFieldsObject( GetTimetableResponseObjectFieldsObject(
id: json['id'] as int?, id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?, name: json['name'] as String?,
longname: json['longname'] as String?, longname: json['longname'] as String?,
externalkey: json['externalkey'] as String?, externalkey: json['externalkey'] as String?,
@@ -128,7 +120,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectFieldsObjectToJson(
GetTimetableResponseObjectClass _$GetTimetableResponseObjectClassFromJson( GetTimetableResponseObjectClass _$GetTimetableResponseObjectClassFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectClass( GetTimetableResponseObjectClass(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
json['externalkey'] as String?, json['externalkey'] as String?,
@@ -146,10 +138,10 @@ Map<String, dynamic> _$GetTimetableResponseObjectClassToJson(
GetTimetableResponseObjectTeacher _$GetTimetableResponseObjectTeacherFromJson( GetTimetableResponseObjectTeacher _$GetTimetableResponseObjectTeacherFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectTeacher( GetTimetableResponseObjectTeacher(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
json['orgid'] as int?, (json['orgid'] as num?)?.toInt(),
json['orgname'] as String?, json['orgname'] as String?,
json['externalkey'] as String?, json['externalkey'] as String?,
); );
@@ -168,7 +160,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectTeacherToJson(
GetTimetableResponseObjectSubject _$GetTimetableResponseObjectSubjectFromJson( GetTimetableResponseObjectSubject _$GetTimetableResponseObjectSubjectFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectSubject( GetTimetableResponseObjectSubject(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
); );
@@ -184,7 +176,7 @@ Map<String, dynamic> _$GetTimetableResponseObjectSubjectToJson(
GetTimetableResponseObjectRoom _$GetTimetableResponseObjectRoomFromJson( GetTimetableResponseObjectRoom _$GetTimetableResponseObjectRoomFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
GetTimetableResponseObjectRoom( GetTimetableResponseObjectRoom(
json['id'] as int, (json['id'] as num).toInt(),
json['name'] as String, json['name'] as String,
json['longname'] as String, json['longname'] as String,
); );

View File

@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'state/app/modules/app_modules.dart'; import 'state/app/modules/app_modules.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:badges/badges.dart' as badges;
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
import 'api/mhsl/server/userIndex/update/updateUserindex.dart'; import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
@@ -25,6 +24,7 @@ import 'storage/base/settingsProvider.dart';
import 'view/pages/overhang.dart'; import 'view/pages/overhang.dart';
class App extends StatefulWidget { class App extends StatefulWidget {
static GlobalKey appContext = GlobalKey();
const App({super.key}); const App({super.key});
@override @override
@@ -32,7 +32,6 @@ class App extends StatefulWidget {
} }
class _AppState extends State<App> with WidgetsBindingObserver { class _AppState extends State<App> with WidgetsBindingObserver {
late Timer refetchChats; late Timer refetchChats;
late Timer updateTimings; late Timer updateTimings;
@@ -93,7 +92,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
} }
@override @override
Widget build(BuildContext context) => PersistentTabView( Widget build(BuildContext context) => Consumer<SettingsProvider>(builder: (context, settings, child) => PersistentTabView(
controller: Main.bottomNavigator, controller: Main.bottomNavigator,
navBarOverlap: const NavBarOverlap.none(), navBarOverlap: const NavBarOverlap.none(),
backgroundColor: Theme.of(context).colorScheme.primary, backgroundColor: Theme.of(context).colorScheme.primary,
@@ -101,29 +100,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)), screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
tabs: [ tabs: [
AppModule.getModule(Modules.timetable).toBottomTab(context), ...AppModule.getBottomBarModules(context).map((e) => e.toBottomTab(context)),
AppModule.getModule(Modules.talk).toBottomTab(
context,
itemBuilder: (icon) => Consumer<ChatListProps>(
builder: (context, value, child) {
if(value.primaryLoading()) return Icon(icon);
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
return badges.Badge(
showBadge: messages > 0,
position: badges.BadgePosition.topEnd(top: -3, end: -3),
stackFit: StackFit.loose,
badgeStyle: badges.BadgeStyle(
padding: const EdgeInsets.all(3),
badgeColor: Theme.of(context).primaryColor,
elevation: 1,
),
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
child: Icon(icon),
);
},
),
),
AppModule.getModule(Modules.files).toBottomTab(context),
PersistentTabConfig( PersistentTabConfig(
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()), screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
@@ -142,7 +119,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
), ),
), ),
); ));
@override @override
void dispose() { void dispose() {

View File

@@ -0,0 +1,64 @@
import 'dart:developer';
import 'package:background_fetch/background_fetch.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../app.dart';
import '../homescreen_widgets/timetable/timetableHomeWidget.dart';
class ScheduledTask {
static final String fetchApiLastRunTimestampKey = 'fetchApiLastRunTimestamp';
static Future<void> configure() async {
var status = await BackgroundFetch.configure(BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY,
startOnBoot: true,
), (String taskId) async {
log('Background fetch started with id $taskId');
await ScheduledTask.backgroundFetch();
BackgroundFetch.finish(taskId);
}, (String taskId) async {
log('Background fetch stopped because of an timeout with id $taskId');
BackgroundFetch.finish(taskId);
});
log('Background Fetch-API status: $status');
}
// called periodically, iOS and Android
static Future<void> backgroundFetch() async {
var sp = await SharedPreferences.getInstance();
var history = sp.getStringList(fetchApiLastRunTimestampKey) ?? List.empty(growable: true);
history.add(DateTime.now().toIso8601String());
try {
TimetableHomeWidget.update(App.appContext.currentContext!);
} on Exception catch(e) {
history.add('Got Error:');
history.add(e.toString());
history.add('--- EXCEPTION END ---');
}
sp.setStringList(fetchApiLastRunTimestampKey, history.take(100).toList());
}
// only Android, starts when app is terminated
@pragma('vm:entry-point')
static Future<void> headless(HeadlessTask task) async {
var taskId = task.taskId;
var isTimeout = task.timeout;
if (isTimeout) {
log('Background fetch headless task timed-out: $taskId');
BackgroundFetch.finish(taskId);
return;
}
log('Background fetch headless event received.');
await backgroundFetch();
BackgroundFetch.finish(taskId);
}
}

View File

@@ -0,0 +1,85 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:home_widget/home_widget.dart';
import 'package:screenshot/screenshot.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import '../../model/accountData.dart';
import '../../model/timetable/timetableProps.dart';
import '../../storage/base/settingsProvider.dart';
import '../../theming/darkAppTheme.dart';
import '../../theming/lightAppTheme.dart';
import '../../view/pages/timetable/calendar.dart';
class TimetableHomeWidget {
static Future<void> update(BuildContext context) async {
await AccountData().waitForPopulation();
var data = TimetableProps();
var settings = SettingsProvider();
settings.waitForPopulation();
var completer = Completer();
data.addListener(() async {
if(completer.isCompleted) return;
if(data.primaryLoading()) return;
await _generate(data, settings);
completer.complete();
});
data.run();
await completer.future;
}
static Future<void> _generate(TimetableProps data, SettingsProvider settings) async {
log('Generating widget screen...');
var screenshotController = ScreenshotController();
var calendarController = CalendarController();
calendarController.displayDate = DateTime.now().copyWith(hour: 07, minute: 00);
var imageData = await screenshotController.captureFromWidget(
SizedBox(
height: 700,
width: 300,
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('de'),
Locale('en'),
],
locale: const Locale('de'),
darkTheme: DarkAppTheme.theme,
theme: LightAppTheme.theme,
themeMode: settings.val().appTheme,
home: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Scaffold(
body: Calendar(
controller: calendarController,
timetableProps: data,
settings: settings,
isHomeWidget: true,
),
),
),
),
),
),
),
);
HomeWidget.saveWidgetData<String>('screen', base64.encode(imageData));
HomeWidget.updateWidget(name: 'TimetableWidget');
log('Widget screen successfully updated! (${imageData.length})');
}
}

View File

@@ -2,21 +2,23 @@ import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:background_fetch/background_fetch.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:loader_overlay/loader_overlay.dart'; import 'package:loader_overlay/loader_overlay.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
import 'app.dart'; import 'app.dart';
import 'background_tasks/scheduledTask.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
import 'model/accountData.dart'; import 'model/accountData.dart';
import 'model/accountModel.dart'; import 'model/accountModel.dart';
@@ -48,7 +50,9 @@ Future<void> main() async {
PlatformAssetBundle().load('assets/ca/lets-encrypt-r10.pem').then(addCertificateAsTrusted), PlatformAssetBundle().load('assets/ca/lets-encrypt-r10.pem').then(addCertificateAsTrusted),
Future(() async { Future(() async {
await HydratedStorage.build(storageDirectory: await getTemporaryDirectory()).then((storage) => HydratedBloc.storage = storage); await HydratedStorage.build(
storageDirectory: HydratedStorageDirectory((await getTemporaryDirectory()).path)
).then((storage) => HydratedBloc.storage = storage);
}) })
]; ];
@@ -82,6 +86,8 @@ Future<void> main() async {
child: const Main(), child: const Main(),
) )
); );
BackgroundFetch.registerHeadlessTask(ScheduledTask.headless);
} }
class Main extends StatefulWidget { class Main extends StatefulWidget {
@@ -109,6 +115,7 @@ class _MainState extends State<Main> {
Provider.of<BreakerProps>(context, listen: false).run(); Provider.of<BreakerProps>(context, listen: false).run();
}); });
ScheduledTask.configure();
super.initState(); super.initState();
} }
@@ -123,7 +130,6 @@ class _MainState extends State<Main> {
checkerboardOffscreenLayers: devToolsSettings.checkerboardOffscreenLayers, checkerboardOffscreenLayers: devToolsSettings.checkerboardOffscreenLayers,
checkerboardRasterCacheImages: devToolsSettings.checkerboardRasterCacheImages, checkerboardRasterCacheImages: devToolsSettings.checkerboardRasterCacheImages,
debugShowCheckedModeBanner: false,
localizationsDelegates: const [ localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates, ...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
@@ -145,7 +151,7 @@ class _MainState extends State<Main> {
child: Consumer<AccountModel>( child: Consumer<AccountModel>(
builder: (context, accountModel, child) { builder: (context, accountModel, child) {
switch(accountModel.state) { switch(accountModel.state) {
case AccountModelState.loggedIn: return const App(); case AccountModelState.loggedIn: return App(key: App.appContext);
case AccountModelState.loggedOut: return const Login(); case AccountModelState.loggedOut: return const Login();
case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: 'Daten werden geladen'); case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: 'Daten werden geladen');
} }

View File

@@ -23,7 +23,11 @@ class _BreakerState extends State<Breaker> {
builder: (context, value, child) { builder: (context, value, child) {
var blocked = value.isBlocked(widget.breaker); var blocked = value.isBlocked(widget.breaker);
if(blocked != null) { if(blocked != null) {
return PlaceholderView(icon: Icons.security_outlined, text: "Die App/ Dieser Bereich wurde als Schutzmaßnahme deaktiviert!\n\n${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt." : blocked}"); return PlaceholderView(
icon: Icons.app_blocking_outlined,
text: 'Die App / Dieser Bereich ist zurzeit nicht verfügbar!\n\n'
"${blocked.isEmpty ? "Es wurde vom Server kein Grund übermittelt.\nAktualisiere die App und versuche es später erneut" : blocked}"
);
} }
return widget.child; return widget.child;

View File

@@ -1,4 +1,4 @@
import 'package:package_info/package_info.dart'; import 'package:package_info_plus/package_info_plus.dart';
import '../../api/apiResponse.dart'; import '../../api/apiResponse.dart';
import '../../api/mhsl/breaker/getBreakers/getBreakersCache.dart'; import '../../api/mhsl/breaker/getBreakers/getBreakersCache.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_app_badge/flutter_app_badge.dart';
import '../../api/apiResponse.dart'; import '../../api/apiResponse.dart';
import '../../api/marianumcloud/talk/room/getRoomCache.dart'; import '../../api/marianumcloud/talk/room/getRoomCache.dart';
@@ -20,7 +20,7 @@ class ChatListProps extends DataHolder {
onUpdate: (GetRoomResponse data) => { onUpdate: (GetRoomResponse data) => {
_getRoomResponse = data, _getRoomResponse = data,
notifyListeners(), notifyListeners(),
FlutterAppBadger.updateBadgeCount(data.data.map((e) => e.unreadMessages).reduce((a, b) => a+b)) FlutterAppBadge.count(data.data.map((e) => e.unreadMessages).reduce((a, b) => a+b))
} }
); );
} }

View File

@@ -44,7 +44,7 @@ class NotificationController {
} }
static Future<void> onAppOpenedByNotification(RemoteMessage message, BuildContext context) async { static Future<void> onAppOpenedByNotification(RemoteMessage message, BuildContext context) async {
NotificationTasks.navigateToTalk(); NotificationTasks.navigateToTalk(context);
NotificationTasks.updateProviders(context); NotificationTasks.updateProviders(context);
DebugTile(context).run(() { DebugTile(context).run(() {

View File

@@ -15,9 +15,6 @@ class NotificationService {
); );
final iosSettings = DarwinInitializationSettings( final iosSettings = DarwinInitializationSettings(
onDidReceiveLocalNotification: (id, title, body, payload) {
// TODO Navigate to Talk section (This runs when an Notification is tapped)
},
); );

View File

@@ -1,15 +1,16 @@
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_app_badge/flutter_app_badge.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
import '../model/chatList/chatListProps.dart'; import '../model/chatList/chatListProps.dart';
import '../model/chatList/chatProps.dart'; import '../model/chatList/chatProps.dart';
import '../state/app/modules/app_modules.dart';
class NotificationTasks { class NotificationTasks {
static void updateBadgeCount(RemoteMessage notification) { static void updateBadgeCount(RemoteMessage notification) {
FlutterAppBadger.updateBadgeCount(int.parse(notification.data['unreadCount'] ?? 0)); FlutterAppBadge.count(int.parse(notification.data['unreadCount'] ?? 0));
} }
static void updateProviders(BuildContext context) { static void updateProviders(BuildContext context) {
@@ -17,7 +18,9 @@ class NotificationTasks {
Provider.of<ChatProps>(context, listen: false).run(); Provider.of<ChatProps>(context, listen: false).run();
} }
static void navigateToTalk() { static void navigateToTalk(BuildContext context) {
Main.bottomNavigator.jumpToTab(1); var talkTab = AppModule.getBottomBarModules(context).map((e) => e.module).toList().indexOf(Modules.talk);
if(talkTab == -1) return;
Main.bottomNavigator.jumpToTab(talkTab);
} }
} }

View File

@@ -0,0 +1,9 @@
import 'package:dio/dio.dart';
import '../../infrastructure/dataLoader/data_loader.dart';
abstract class HolidayDataLoader<TResult> extends DataLoader<TResult> {
HolidayDataLoader() : super(Dio(BaseOptions(
baseUrl: 'https://ferien-api.de/api/v1/',
)));
}

View File

@@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'data_loader.dart'; import '../../infrastructure/dataLoader/data_loader.dart';
abstract class MhslDataLoader<TResult> extends DataLoader<TResult> { abstract class MhslDataLoader<TResult> extends DataLoader<TResult> {
MhslDataLoader() : super(Dio(BaseOptions( MhslDataLoader() : super(Dio(BaseOptions(

View File

@@ -35,8 +35,12 @@ abstract class DataLoader<TResult> {
} }
class DataLoaderResult { class DataLoaderResult {
final Map<String, dynamic> json; final dynamic json;
final Map<String, String> headers; final Map<String, String> headers;
Map<String, dynamic> asMap() => json as Map<String, dynamic>;
List<dynamic> asList() => json as List<dynamic>;
List<Map<String, dynamic>> asListOfMaps() => asList().map((e) => e as Map<String, dynamic>).toList();
DataLoaderResult({required this.json, required this.headers}); DataLoaderResult({required this.json, required this.headers});
} }

View File

@@ -19,7 +19,9 @@ mixin _$LoadableStateState {
List<ConnectivityResult>? get connections => List<ConnectivityResult>? get connections =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of LoadableStateState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoadableStateStateCopyWith<LoadableStateState> get copyWith => $LoadableStateStateCopyWith<LoadableStateState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -43,6 +45,8 @@ class _$LoadableStateStateCopyWithImpl<$Res, $Val extends LoadableStateState>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of LoadableStateState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -76,6 +80,8 @@ class __$$LoadableStateStateImplCopyWithImpl<$Res>
$Res Function(_$LoadableStateStateImpl) _then) $Res Function(_$LoadableStateStateImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of LoadableStateState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -125,7 +131,9 @@ class _$LoadableStateStateImpl implements _LoadableStateState {
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_connections)); runtimeType, const DeepCollectionEquality().hash(_connections));
@JsonKey(ignore: true) /// Create a copy of LoadableStateState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith => _$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith =>
@@ -140,8 +148,11 @@ abstract class _LoadableStateState implements LoadableStateState {
@override @override
List<ConnectivityResult>? get connections; List<ConnectivityResult>? get connections;
/// Create a copy of LoadableStateState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith => _$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -9,11 +9,11 @@ class LoadableState<TState> with _$LoadableState {
const LoadableState._(); const LoadableState._();
const factory LoadableState({ const factory LoadableState({
@Default(true) bool isLoading, required bool isLoading,
@Default(null) TState? data, required TState? data,
@Default(null) int? lastFetch, required int? lastFetch,
@Default(null) void Function()? reFetch, required void Function()? reFetch,
@Default(null) LoadingError? error, required LoadingError? error,
}) = _LoadableState; }) = _LoadableState;
bool _hasError() => error != null; bool _hasError() => error != null;

View File

@@ -22,7 +22,9 @@ mixin _$LoadableState<TState> {
void Function()? get reFetch => throw _privateConstructorUsedError; void Function()? get reFetch => throw _privateConstructorUsedError;
LoadingError? get error => throw _privateConstructorUsedError; LoadingError? get error => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoadableStateCopyWith<TState, LoadableState<TState>> get copyWith => $LoadableStateCopyWith<TState, LoadableState<TState>> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -54,6 +56,8 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -87,6 +91,8 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
) as $Val); ) as $Val);
} }
/// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$LoadingErrorCopyWith<$Res>? get error { $LoadingErrorCopyWith<$Res>? get error {
@@ -128,6 +134,8 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
$Res Function(_$LoadableStateImpl<TState>) _then) $Res Function(_$LoadableStateImpl<TState>) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -166,27 +174,22 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
class _$LoadableStateImpl<TState> extends _LoadableState<TState> { class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
const _$LoadableStateImpl( const _$LoadableStateImpl(
{this.isLoading = true, {required this.isLoading,
this.data = null, required this.data,
this.lastFetch = null, required this.lastFetch,
this.reFetch = null, required this.reFetch,
this.error = null}) required this.error})
: super._(); : super._();
@override @override
@JsonKey()
final bool isLoading; final bool isLoading;
@override @override
@JsonKey()
final TState? data; final TState? data;
@override @override
@JsonKey()
final int? lastFetch; final int? lastFetch;
@override @override
@JsonKey()
final void Function()? reFetch; final void Function()? reFetch;
@override @override
@JsonKey()
final LoadingError? error; final LoadingError? error;
@override @override
@@ -212,7 +215,9 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
int get hashCode => Object.hash(runtimeType, isLoading, int get hashCode => Object.hash(runtimeType, isLoading,
const DeepCollectionEquality().hash(data), lastFetch, reFetch, error); const DeepCollectionEquality().hash(data), lastFetch, reFetch, error);
@JsonKey(ignore: true) /// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>> _$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>>
@@ -222,11 +227,11 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
abstract class _LoadableState<TState> extends LoadableState<TState> { abstract class _LoadableState<TState> extends LoadableState<TState> {
const factory _LoadableState( const factory _LoadableState(
{final bool isLoading, {required final bool isLoading,
final TState? data, required final TState? data,
final int? lastFetch, required final int? lastFetch,
final void Function()? reFetch, required final void Function()? reFetch,
final LoadingError? error}) = _$LoadableStateImpl<TState>; required final LoadingError? error}) = _$LoadableStateImpl<TState>;
const _LoadableState._() : super._(); const _LoadableState._() : super._();
@override @override
@@ -239,8 +244,11 @@ abstract class _LoadableState<TState> extends LoadableState<TState> {
void Function()? get reFetch; void Function()? get reFetch;
@override @override
LoadingError? get error; LoadingError? get error;
/// Create a copy of LoadableState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>> _$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }

View File

@@ -19,7 +19,9 @@ mixin _$LoadingError {
String get message => throw _privateConstructorUsedError; String get message => throw _privateConstructorUsedError;
bool get allowRetry => throw _privateConstructorUsedError; bool get allowRetry => throw _privateConstructorUsedError;
@JsonKey(ignore: true) /// Create a copy of LoadingError
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoadingErrorCopyWith<LoadingError> get copyWith => $LoadingErrorCopyWith<LoadingError> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -43,6 +45,8 @@ class _$LoadingErrorCopyWithImpl<$Res, $Val extends LoadingError>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of LoadingError
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -81,6 +85,8 @@ class __$$LoadingErrorImplCopyWithImpl<$Res>
_$LoadingErrorImpl _value, $Res Function(_$LoadingErrorImpl) _then) _$LoadingErrorImpl _value, $Res Function(_$LoadingErrorImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of LoadingError
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -129,7 +135,9 @@ class _$LoadingErrorImpl implements _LoadingError {
@override @override
int get hashCode => Object.hash(runtimeType, message, allowRetry); int get hashCode => Object.hash(runtimeType, message, allowRetry);
@JsonKey(ignore: true) /// Create a copy of LoadingError
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith => _$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
@@ -145,8 +153,11 @@ abstract class _LoadingError implements LoadingError {
String get message; String get message;
@override @override
bool get allowRetry; bool get allowRetry;
/// Create a copy of LoadingError
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith => _$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -15,14 +15,20 @@ import 'loadable_state_primary_loading.dart';
class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget { class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget {
final Widget Function(TState state, bool loading) child; final Widget Function(TState state, bool loading) child;
final void Function(TState state)? onLoad;
final bool wrapWithScrollView; final bool wrapWithScrollView;
const LoadableStateConsumer({required this.child, this.wrapWithScrollView = false, super.key}); const LoadableStateConsumer({required this.child, this.onLoad, this.wrapWithScrollView = false, super.key});
static Duration animationDuration = const Duration(milliseconds: 200); static Duration animationDuration = const Duration(milliseconds: 200);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var loadableState = context.watch<TController>().state; var loadableState = context.watch<TController>().state;
if(!loadableState.isLoading && onLoad != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => onLoad!(loadableState.data));
}
var childWidget = ConditionalWrapper( var childWidget = ConditionalWrapper(
condition: loadableState.reFetch != null, condition: loadableState.reFetch != null,
wrapper: (child) => RefreshIndicator( wrapper: (child) => RefreshIndicator(

View File

@@ -89,4 +89,3 @@ class _LoadableStateErrorBarTextState extends State<LoadableStateErrorBarText> {
super.dispose(); super.dispose();
} }
} }

View File

@@ -5,14 +5,19 @@ class BlocModule<TBloc extends StateStreamableSource<TState>, TState> extends St
final TBloc Function(BuildContext context) create; final TBloc Function(BuildContext context) create;
final Widget Function(BuildContext context, TBloc bloc, TState state) child; final Widget Function(BuildContext context, TBloc bloc, TState state) child;
final bool autoRebuild; final bool autoRebuild;
const BlocModule({required this.create, required this.child, this.autoRebuild = false, super.key}); final void Function(BuildContext context, TBloc bloc)? onInitialisation;
const BlocModule({required this.create, required this.child, this.autoRebuild = false, this.onInitialisation, super.key});
Widget rebuildChild(BuildContext context) => child(context, context.watch<TBloc>(), context.watch<TBloc>().state); Widget rebuildChild(BuildContext context) => child(context, context.watch<TBloc>(), context.watch<TBloc>().state);
Widget staticChild(BuildContext context) => child(context, context.read<TBloc>(), context.read<TBloc>().state); Widget staticChild(BuildContext context) => child(context, context.read<TBloc>(), context.read<TBloc>().state);
@override @override
Widget build(BuildContext context) => BlocProvider<TBloc>( Widget build(BuildContext context) => BlocProvider<TBloc>(
create: create, create: (context) {
var bloc = create(context);
this.onInitialisation != null ? this.onInitialisation!(context, bloc) : null;
return bloc;
},
child: Builder( child: Builder(
builder: (context) => autoRebuild builder: (context) => autoRebuild
? rebuildChild(context) ? rebuildChild(context)

View File

@@ -17,23 +17,40 @@ abstract class LoadableHydratedBloc<
LoadableState<TState> LoadableState<TState>
> { > {
late TRepository _repository; late TRepository _repository;
LoadableHydratedBloc() : super(const LoadableState()) { LoadableHydratedBloc() : super(const LoadableState(
error: null,
data: null,
isLoading: true,
lastFetch: null,
reFetch: null,
)) {
on<Emit<TState>>((event, emit) => emit(LoadableState( on<Emit<TState>>((event, emit) {
isLoading: event.loading, emit(LoadableState(
isLoading: state.isLoading,
data: event.state(innerState ?? fromNothing()),
lastFetch: state.lastFetch,
reFetch: retry,
error: state.error,
));
});
on<DataGathered<TState>>((event, emit) => emit(LoadableState(
isLoading: false,
data: event.state(innerState ?? fromNothing()), data: event.state(innerState ?? fromNothing()),
lastFetch: DateTime.now().millisecondsSinceEpoch, lastFetch: DateTime.now().millisecondsSinceEpoch,
reFetch: retry reFetch: retry,
error: null,
))); )));
on<RefetchStarted<TState>>((event, emit) => emit(LoadableState( on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(
isLoading: true, isLoading: true,
data: innerState, data: innerState,
lastFetch: state.lastFetch lastFetch: state.lastFetch,
reFetch: null,
error: null,
))); )));
on<ClearState<TState>>((event, emit) => emit(const LoadableState()));
on<Error<TState>>((event, emit) => emit(LoadableState( on<Error<TState>>((event, emit) => emit(LoadableState(
isLoading: false, isLoading: false,
data: innerState, data: innerState,
@@ -61,7 +78,7 @@ abstract class LoadableHydratedBloc<
(e) { (e) {
log('Error while fetching ${TState.toString()}: ${e.toString()}'); log('Error while fetching ${TState.toString()}: ${e.toString()}');
add(Error(LoadingError( add(Error(LoadingError(
message: e.message, message: e.message ?? e.toString(),
allowRetry: true, allowRetry: true,
))); )));
}, },
@@ -73,14 +90,29 @@ abstract class LoadableHydratedBloc<
@override @override
fromJson(Map<String, dynamic> json) { fromJson(Map<String, dynamic> json) {
var rawData = LoadableSaveContext.unwrap(json); var rawData = LoadableSaveContext.unwrap(json);
return LoadableState(isLoading: true, lastFetch: rawData.meta.timestamp, data: fromStorage(rawData.data)); return LoadableState(
isLoading: true,
data: fromStorage(rawData.data),
lastFetch: rawData.meta.timestamp,
reFetch: null,
error: null,
);
} }
@override @override
Map<String, dynamic>? toJson(LoadableState<TState> state) => LoadableSaveContext.wrap( Map<String, dynamic>? toJson(LoadableState<TState> state) {
toStorage(state.data), Map<String, dynamic>? data;
try {
data = state.data == null ? null : toStorage(state.data);
} catch(e) {
log('Failed to save state ${TState.toString()}: ${e.toString()}');
}
return LoadableSaveContext.wrap(
data,
state.lastFetch ?? DateTime.now().millisecondsSinceEpoch state.lastFetch ?? DateTime.now().millisecondsSinceEpoch
); );
}
Future<void> gatherData(); Future<void> gatherData();
TRepository repository(); TRepository repository();

View File

@@ -3,10 +3,12 @@ import '../../loadableState/loading_error.dart';
class LoadableHydratedBlocEvent<TState> {} class LoadableHydratedBlocEvent<TState> {}
class Emit<TState> extends LoadableHydratedBlocEvent<TState> { class Emit<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state; final TState Function(TState state) state;
final bool loading; Emit(this.state);
Emit(this.state, {this.loading = false}); }
class DataGathered<TState> extends LoadableHydratedBlocEvent<TState> {
final TState Function(TState state) state;
DataGathered(this.state);
} }
class ClearState<TState> extends LoadableHydratedBlocEvent<TState> {}
class Error<TState> extends LoadableHydratedBlocEvent<TState> { class Error<TState> extends LoadableHydratedBlocEvent<TState> {
final LoadingError error; final LoadingError error;
Error(this.error); Error(this.error);

View File

@@ -21,4 +21,3 @@ class LoadableSaveContext with _$LoadableSaveContext {
static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) => static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) =>
(data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey])); (data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey]));
} }

View File

@@ -22,8 +22,12 @@ LoadableSaveContext _$LoadableSaveContextFromJson(Map<String, dynamic> json) {
mixin _$LoadableSaveContext { mixin _$LoadableSaveContext {
int get timestamp => throw _privateConstructorUsedError; int get timestamp => throw _privateConstructorUsedError;
/// Serializes this LoadableSaveContext to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of LoadableSaveContext
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoadableSaveContextCopyWith<LoadableSaveContext> get copyWith => $LoadableSaveContextCopyWith<LoadableSaveContext> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -47,6 +51,8 @@ class _$LoadableSaveContextCopyWithImpl<$Res, $Val extends LoadableSaveContext>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of LoadableSaveContext
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -80,6 +86,8 @@ class __$$LoadableSaveContextImplCopyWithImpl<$Res>
$Res Function(_$LoadableSaveContextImpl) _then) $Res Function(_$LoadableSaveContextImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of LoadableSaveContext
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -119,11 +127,13 @@ class _$LoadableSaveContextImpl extends _LoadableSaveContext {
other.timestamp == timestamp)); other.timestamp == timestamp));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, timestamp); int get hashCode => Object.hash(runtimeType, timestamp);
@JsonKey(ignore: true) /// Create a copy of LoadableSaveContext
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith => _$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith =>
@@ -148,8 +158,11 @@ abstract class _LoadableSaveContext extends LoadableSaveContext {
@override @override
int get timestamp; int get timestamp;
/// Create a copy of LoadableSaveContext
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith => _$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -9,7 +9,7 @@ part of 'loadable_save_context.dart';
_$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson( _$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
_$LoadableSaveContextImpl( _$LoadableSaveContextImpl(
timestamp: json['timestamp'] as int, timestamp: (json['timestamp'] as num).toInt(),
); );
Map<String, dynamic> _$$LoadableSaveContextImplToJson( Map<String, dynamic> _$$LoadableSaveContextImplToJson(

View File

@@ -1,49 +1,129 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
import 'package:provider/provider.dart';
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart'; import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
import '../../../model/breakers/Breaker.dart'; import '../../../model/breakers/Breaker.dart';
import '../../../model/chatList/chatListProps.dart';
import '../../../storage/base/settingsProvider.dart';
import '../../../view/pages/files/files.dart'; import '../../../view/pages/files/files.dart';
import '../../../view/pages/more/holidays/holidays.dart';
import '../../../view/pages/more/roomplan/roomplan.dart'; import '../../../view/pages/more/roomplan/roomplan.dart';
import '../../../view/pages/talk/chatList.dart'; import '../../../view/pages/talk/chatList.dart';
import '../../../view/pages/timetable/timetable.dart'; import '../../../view/pages/timetable/timetable.dart';
import '../../../widget/centeredLeading.dart'; import '../../../widget/centeredLeading.dart';
import 'gradeAverages/view/grade_averages_view.dart'; import 'gradeAverages/view/grade_averages_view.dart';
import 'holidays/view/holidays_view.dart';
import 'marianumMessage/view/marianum_message_list_view.dart'; import 'marianumMessage/view/marianum_message_list_view.dart';
import 'package:badges/badges.dart' as badges;
class AppModule { class AppModule {
Modules module;
String name; String name;
IconData icon; Widget Function() icon;
BreakerArea breakerArea;
Widget Function() create; Widget Function() create;
AppModule(this.name, this.icon, this.create); AppModule(this.module, {required this.name, required this.icon, this.breakerArea = BreakerArea.global, required this.create});
static Map<Modules, AppModule> modules() => { static Map<Modules, AppModule> modules(BuildContext context, { showFiltered = false }) {
Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new), var settings = Provider.of<SettingsProvider>(context, listen: false);
Modules.talk: AppModule('Talk', Icons.chat, ChatList.new), var available = {
Modules.files: AppModule('Files', Icons.folder, Files.new), Modules.timetable: AppModule(
Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new), Modules.timetable,
Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new), name: 'Vertretung',
Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new), icon: () => Icon(Icons.calendar_month),
Modules.holidays: AppModule('Schulferien', Icons.holiday_village, Holidays.new), breakerArea: BreakerArea.timetable,
create: Timetable.new,
),
Modules.talk: AppModule(
Modules.talk,
name: 'Talk',
icon: () => Consumer<ChatListProps>(
builder: (context, value, child) {
if(value.primaryLoading()) return Icon(Icons.chat);
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
return badges.Badge(
showBadge: messages > 0,
position: badges.BadgePosition.topEnd(top: -3, end: -3),
stackFit: StackFit.loose,
badgeStyle: badges.BadgeStyle(
padding: const EdgeInsets.all(3),
badgeColor: Theme.of(context).primaryColor,
elevation: 1,
),
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
child: Icon(Icons.chat),
);
},
),
breakerArea: BreakerArea.talk,
create: ChatList.new,
),
Modules.files: AppModule(
Modules.files,
name: 'Files',
icon: () => Icon(Icons.folder),
breakerArea: BreakerArea.files,
create: Files.new,
),
Modules.marianumMessage: AppModule(
Modules.marianumMessage,
name: 'Marianum Message',
icon: () => Icon(Icons.newspaper),
breakerArea: BreakerArea.more,
create: MarianumMessageListView.new,
),
Modules.roomPlan: AppModule(
Modules.roomPlan,
name: 'Raumplan',
icon: () => Icon(Icons.location_pin),
breakerArea: BreakerArea.more,
create: Roomplan.new,
),
Modules.gradeAveragesCalculator: AppModule(
Modules.gradeAveragesCalculator,
name: 'Notendurschnittsrechner',
icon: () => Icon(Icons.calculate),
breakerArea: BreakerArea.more,
create: GradeAveragesView.new,
),
Modules.holidays: AppModule(
Modules.holidays,
name: 'Schulferien',
icon: () => Icon(Icons.flight),
breakerArea: BreakerArea.more,
create: HolidaysView.new,
),
}; };
static AppModule getModule(Modules module) => modules()[module]!; if(!showFiltered) available.removeWhere((key, value) => settings.val().modulesSettings.hiddenModules.contains(key));
Widget toListTile(BuildContext context) => ListTile( return { for (var element in settings.val().modulesSettings.moduleOrder.where((element) => available.containsKey(element))) element : available[element]! };
leading: CenteredLeading(Icon(icon)), }
static List<AppModule> getBottomBarModules(BuildContext context) => modules(context).values.toList().getRange(0, 3).toList();
static List<AppModule> getOverhangModules(BuildContext context) => modules(context).values.skip(3).toList();
Widget toListTile(BuildContext context, {Key? key, bool isReorder = false, Function()? onVisibleChange, bool isVisible = true}) => ListTile(
key: key,
leading: CenteredLeading(icon()),
title: Text(name), title: Text(name),
onTap: () => pushScreen(context, withNavBar: false, screen: create()), onTap: isReorder ? null : () => pushScreen(context, withNavBar: false, screen: create()),
trailing: const Icon(Icons.arrow_right), trailing: isReorder
? Row(mainAxisSize: MainAxisSize.min, children: [
IconButton(onPressed: onVisibleChange, icon: Icon(isVisible ? Icons.visibility_outlined : Icons.visibility_off_outlined)),
Icon(Icons.drag_handle_outlined)
])
: const Icon(Icons.arrow_right),
); );
PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? itemBuilder}) => PersistentTabConfig( PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? iconBuilder}) => PersistentTabConfig(
screen: Breaker(breaker: BreakerArea.global, child: create()), screen: Breaker(breaker: breakerArea, child: create()),
item: ItemConfig( item: ItemConfig(
activeForegroundColor: Theme.of(context).primaryColor, activeForegroundColor: Theme.of(context).primaryColor,
inactiveForegroundColor: Theme.of(context).colorScheme.secondary, inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
icon: itemBuilder == null ? Icon(icon) : itemBuilder(icon), icon: icon(),
title: name title: name
), ),
); );

View File

@@ -24,8 +24,12 @@ mixin _$GradeAveragesState {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
List<int> get grades => throw _privateConstructorUsedError; List<int> get grades => throw _privateConstructorUsedError;
/// Serializes this GradeAveragesState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of GradeAveragesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$GradeAveragesStateCopyWith<GradeAveragesState> get copyWith => $GradeAveragesStateCopyWith<GradeAveragesState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -49,6 +53,8 @@ class _$GradeAveragesStateCopyWithImpl<$Res, $Val extends GradeAveragesState>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of GradeAveragesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -87,6 +93,8 @@ class __$$GradeAveragesStateImplCopyWithImpl<$Res>
$Res Function(_$GradeAveragesStateImpl) _then) $Res Function(_$GradeAveragesStateImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of GradeAveragesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -141,12 +149,14 @@ class _$GradeAveragesStateImpl implements _GradeAveragesState {
const DeepCollectionEquality().equals(other._grades, _grades)); const DeepCollectionEquality().equals(other._grades, _grades));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, gradingSystem, const DeepCollectionEquality().hash(_grades)); runtimeType, gradingSystem, const DeepCollectionEquality().hash(_grades));
@JsonKey(ignore: true) /// Create a copy of GradeAveragesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith => _$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith =>
@@ -173,8 +183,11 @@ abstract class _GradeAveragesState implements GradeAveragesState {
GradeAveragesGradingSystem get gradingSystem; GradeAveragesGradingSystem get gradingSystem;
@override @override
List<int> get grades; List<int> get grades;
/// Create a copy of GradeAveragesState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith => _$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -11,7 +11,9 @@ _$GradeAveragesStateImpl _$$GradeAveragesStateImplFromJson(
_$GradeAveragesStateImpl( _$GradeAveragesStateImpl(
gradingSystem: $enumDecode( gradingSystem: $enumDecode(
_$GradeAveragesGradingSystemEnumMap, json['gradingSystem']), _$GradeAveragesGradingSystemEnumMap, json['gradingSystem']),
grades: (json['grades'] as List<dynamic>).map((e) => e as int).toList(), grades: (json['grades'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
); );
Map<String, dynamic> _$$GradeAveragesStateImplToJson( Map<String, dynamic> _$$GradeAveragesStateImplToJson(

View File

@@ -0,0 +1,37 @@
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import '../repository/holidays_repository.dart';
import 'holidays_event.dart';
import 'holidays_state.dart';
class HolidaysBloc extends LoadableHydratedBloc<HolidaysEvent, HolidaysState, HolidaysRepository> {
HolidaysBloc() {
on<SetPastHolidaysVisible>((event, emit) {
add(Emit((state) => state.copyWith(showPastHolidays: event.shouldBeVisible)));
});
on<DisclaimerDismissed>((event, emit) => add(
Emit((state) => state.copyWith(showDisclaimer: false))
));
}
bool showPastHolidays() => innerState?.showPastHolidays ?? false;
bool showDisclaimerOnEntry() => innerState?.showDisclaimer ?? false;
List<Holiday>? getHolidays() => innerState?.holidays
.where((element) => showPastHolidays() || DateTime.parse(element.end).isAfter(DateTime.now()))
.toList() ?? [];
@override
fromNothing() => const HolidaysState(showPastHolidays: false, holidays: [], showDisclaimer: true);
@override
fromStorage(Map<String, dynamic> json) => HolidaysState.fromJson(json);
@override
Future<void> gatherData() async {
var holidays = await repo.getHolidays();
add(DataGathered((state) => state.copyWith(holidays: holidays)));
}
@override
repository() => HolidaysRepository();
@override
Map<String, dynamic>? toStorage(state) => state.toJson();
}

View File

@@ -0,0 +1,9 @@
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import 'holidays_state.dart';
sealed class HolidaysEvent extends LoadableHydratedBlocEvent<HolidaysState> {}
class SetPastHolidaysVisible extends HolidaysEvent {
final bool shouldBeVisible;
SetPastHolidaysVisible(this.shouldBeVisible);
}
class DisclaimerDismissed extends HolidaysEvent {}

View File

@@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'holidays_state.freezed.dart';
part 'holidays_state.g.dart';
@freezed
class HolidaysState with _$HolidaysState {
const factory HolidaysState({
required bool showPastHolidays,
required bool showDisclaimer,
required List<Holiday> holidays,
}) = _HolidaysState;
factory HolidaysState.fromJson(Map<String, Object?> json) => _$HolidaysStateFromJson(json);
}
@freezed
class Holiday with _$Holiday {
const factory Holiday({
required String start,
required String end,
required int year,
required String stateCode,
required String name,
required String slug,
}) = _Holiday;
factory Holiday.fromJson(Map<String, Object?> json) => _$HolidayFromJson(json);
}

View File

@@ -0,0 +1,489 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'holidays_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
HolidaysState _$HolidaysStateFromJson(Map<String, dynamic> json) {
return _HolidaysState.fromJson(json);
}
/// @nodoc
mixin _$HolidaysState {
bool get showPastHolidays => throw _privateConstructorUsedError;
bool get showDisclaimer => throw _privateConstructorUsedError;
List<Holiday> get holidays => throw _privateConstructorUsedError;
/// Serializes this HolidaysState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of HolidaysState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HolidaysStateCopyWith<HolidaysState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HolidaysStateCopyWith<$Res> {
factory $HolidaysStateCopyWith(
HolidaysState value, $Res Function(HolidaysState) then) =
_$HolidaysStateCopyWithImpl<$Res, HolidaysState>;
@useResult
$Res call(
{bool showPastHolidays, bool showDisclaimer, List<Holiday> holidays});
}
/// @nodoc
class _$HolidaysStateCopyWithImpl<$Res, $Val extends HolidaysState>
implements $HolidaysStateCopyWith<$Res> {
_$HolidaysStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of HolidaysState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? showPastHolidays = null,
Object? showDisclaimer = null,
Object? holidays = null,
}) {
return _then(_value.copyWith(
showPastHolidays: null == showPastHolidays
? _value.showPastHolidays
: showPastHolidays // ignore: cast_nullable_to_non_nullable
as bool,
showDisclaimer: null == showDisclaimer
? _value.showDisclaimer
: showDisclaimer // ignore: cast_nullable_to_non_nullable
as bool,
holidays: null == holidays
? _value.holidays
: holidays // ignore: cast_nullable_to_non_nullable
as List<Holiday>,
) as $Val);
}
}
/// @nodoc
abstract class _$$HolidaysStateImplCopyWith<$Res>
implements $HolidaysStateCopyWith<$Res> {
factory _$$HolidaysStateImplCopyWith(
_$HolidaysStateImpl value, $Res Function(_$HolidaysStateImpl) then) =
__$$HolidaysStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{bool showPastHolidays, bool showDisclaimer, List<Holiday> holidays});
}
/// @nodoc
class __$$HolidaysStateImplCopyWithImpl<$Res>
extends _$HolidaysStateCopyWithImpl<$Res, _$HolidaysStateImpl>
implements _$$HolidaysStateImplCopyWith<$Res> {
__$$HolidaysStateImplCopyWithImpl(
_$HolidaysStateImpl _value, $Res Function(_$HolidaysStateImpl) _then)
: super(_value, _then);
/// Create a copy of HolidaysState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? showPastHolidays = null,
Object? showDisclaimer = null,
Object? holidays = null,
}) {
return _then(_$HolidaysStateImpl(
showPastHolidays: null == showPastHolidays
? _value.showPastHolidays
: showPastHolidays // ignore: cast_nullable_to_non_nullable
as bool,
showDisclaimer: null == showDisclaimer
? _value.showDisclaimer
: showDisclaimer // ignore: cast_nullable_to_non_nullable
as bool,
holidays: null == holidays
? _value._holidays
: holidays // ignore: cast_nullable_to_non_nullable
as List<Holiday>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HolidaysStateImpl
with DiagnosticableTreeMixin
implements _HolidaysState {
const _$HolidaysStateImpl(
{required this.showPastHolidays,
required this.showDisclaimer,
required final List<Holiday> holidays})
: _holidays = holidays;
factory _$HolidaysStateImpl.fromJson(Map<String, dynamic> json) =>
_$$HolidaysStateImplFromJson(json);
@override
final bool showPastHolidays;
@override
final bool showDisclaimer;
final List<Holiday> _holidays;
@override
List<Holiday> get holidays {
if (_holidays is EqualUnmodifiableListView) return _holidays;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_holidays);
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'HolidaysState(showPastHolidays: $showPastHolidays, showDisclaimer: $showDisclaimer, holidays: $holidays)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'HolidaysState'))
..add(DiagnosticsProperty('showPastHolidays', showPastHolidays))
..add(DiagnosticsProperty('showDisclaimer', showDisclaimer))
..add(DiagnosticsProperty('holidays', holidays));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HolidaysStateImpl &&
(identical(other.showPastHolidays, showPastHolidays) ||
other.showPastHolidays == showPastHolidays) &&
(identical(other.showDisclaimer, showDisclaimer) ||
other.showDisclaimer == showDisclaimer) &&
const DeepCollectionEquality().equals(other._holidays, _holidays));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, showPastHolidays, showDisclaimer,
const DeepCollectionEquality().hash(_holidays));
/// Create a copy of HolidaysState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HolidaysStateImplCopyWith<_$HolidaysStateImpl> get copyWith =>
__$$HolidaysStateImplCopyWithImpl<_$HolidaysStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HolidaysStateImplToJson(
this,
);
}
}
abstract class _HolidaysState implements HolidaysState {
const factory _HolidaysState(
{required final bool showPastHolidays,
required final bool showDisclaimer,
required final List<Holiday> holidays}) = _$HolidaysStateImpl;
factory _HolidaysState.fromJson(Map<String, dynamic> json) =
_$HolidaysStateImpl.fromJson;
@override
bool get showPastHolidays;
@override
bool get showDisclaimer;
@override
List<Holiday> get holidays;
/// Create a copy of HolidaysState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HolidaysStateImplCopyWith<_$HolidaysStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
Holiday _$HolidayFromJson(Map<String, dynamic> json) {
return _Holiday.fromJson(json);
}
/// @nodoc
mixin _$Holiday {
String get start => throw _privateConstructorUsedError;
String get end => throw _privateConstructorUsedError;
int get year => throw _privateConstructorUsedError;
String get stateCode => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String get slug => throw _privateConstructorUsedError;
/// Serializes this Holiday to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of Holiday
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$HolidayCopyWith<Holiday> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $HolidayCopyWith<$Res> {
factory $HolidayCopyWith(Holiday value, $Res Function(Holiday) then) =
_$HolidayCopyWithImpl<$Res, Holiday>;
@useResult
$Res call(
{String start,
String end,
int year,
String stateCode,
String name,
String slug});
}
/// @nodoc
class _$HolidayCopyWithImpl<$Res, $Val extends Holiday>
implements $HolidayCopyWith<$Res> {
_$HolidayCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Holiday
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? start = null,
Object? end = null,
Object? year = null,
Object? stateCode = null,
Object? name = null,
Object? slug = null,
}) {
return _then(_value.copyWith(
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as String,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as String,
year: null == year
? _value.year
: year // ignore: cast_nullable_to_non_nullable
as int,
stateCode: null == stateCode
? _value.stateCode
: stateCode // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$HolidayImplCopyWith<$Res> implements $HolidayCopyWith<$Res> {
factory _$$HolidayImplCopyWith(
_$HolidayImpl value, $Res Function(_$HolidayImpl) then) =
__$$HolidayImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String start,
String end,
int year,
String stateCode,
String name,
String slug});
}
/// @nodoc
class __$$HolidayImplCopyWithImpl<$Res>
extends _$HolidayCopyWithImpl<$Res, _$HolidayImpl>
implements _$$HolidayImplCopyWith<$Res> {
__$$HolidayImplCopyWithImpl(
_$HolidayImpl _value, $Res Function(_$HolidayImpl) _then)
: super(_value, _then);
/// Create a copy of Holiday
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? start = null,
Object? end = null,
Object? year = null,
Object? stateCode = null,
Object? name = null,
Object? slug = null,
}) {
return _then(_$HolidayImpl(
start: null == start
? _value.start
: start // ignore: cast_nullable_to_non_nullable
as String,
end: null == end
? _value.end
: end // ignore: cast_nullable_to_non_nullable
as String,
year: null == year
? _value.year
: year // ignore: cast_nullable_to_non_nullable
as int,
stateCode: null == stateCode
? _value.stateCode
: stateCode // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
slug: null == slug
? _value.slug
: slug // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$HolidayImpl with DiagnosticableTreeMixin implements _Holiday {
const _$HolidayImpl(
{required this.start,
required this.end,
required this.year,
required this.stateCode,
required this.name,
required this.slug});
factory _$HolidayImpl.fromJson(Map<String, dynamic> json) =>
_$$HolidayImplFromJson(json);
@override
final String start;
@override
final String end;
@override
final int year;
@override
final String stateCode;
@override
final String name;
@override
final String slug;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'Holiday(start: $start, end: $end, year: $year, stateCode: $stateCode, name: $name, slug: $slug)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'Holiday'))
..add(DiagnosticsProperty('start', start))
..add(DiagnosticsProperty('end', end))
..add(DiagnosticsProperty('year', year))
..add(DiagnosticsProperty('stateCode', stateCode))
..add(DiagnosticsProperty('name', name))
..add(DiagnosticsProperty('slug', slug));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$HolidayImpl &&
(identical(other.start, start) || other.start == start) &&
(identical(other.end, end) || other.end == end) &&
(identical(other.year, year) || other.year == year) &&
(identical(other.stateCode, stateCode) ||
other.stateCode == stateCode) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.slug, slug) || other.slug == slug));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, start, end, year, stateCode, name, slug);
/// Create a copy of Holiday
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$HolidayImplCopyWith<_$HolidayImpl> get copyWith =>
__$$HolidayImplCopyWithImpl<_$HolidayImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$HolidayImplToJson(
this,
);
}
}
abstract class _Holiday implements Holiday {
const factory _Holiday(
{required final String start,
required final String end,
required final int year,
required final String stateCode,
required final String name,
required final String slug}) = _$HolidayImpl;
factory _Holiday.fromJson(Map<String, dynamic> json) = _$HolidayImpl.fromJson;
@override
String get start;
@override
String get end;
@override
int get year;
@override
String get stateCode;
@override
String get name;
@override
String get slug;
/// Create a copy of Holiday
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$HolidayImplCopyWith<_$HolidayImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,43 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'holidays_state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$HolidaysStateImpl _$$HolidaysStateImplFromJson(Map<String, dynamic> json) =>
_$HolidaysStateImpl(
showPastHolidays: json['showPastHolidays'] as bool,
showDisclaimer: json['showDisclaimer'] as bool,
holidays: (json['holidays'] as List<dynamic>)
.map((e) => Holiday.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$HolidaysStateImplToJson(_$HolidaysStateImpl instance) =>
<String, dynamic>{
'showPastHolidays': instance.showPastHolidays,
'showDisclaimer': instance.showDisclaimer,
'holidays': instance.holidays,
};
_$HolidayImpl _$$HolidayImplFromJson(Map<String, dynamic> json) =>
_$HolidayImpl(
start: json['start'] as String,
end: json['end'] as String,
year: (json['year'] as num).toInt(),
stateCode: json['stateCode'] as String,
name: json['name'] as String,
slug: json['slug'] as String,
);
Map<String, dynamic> _$$HolidayImplToJson(_$HolidayImpl instance) =>
<String, dynamic>{
'start': instance.start,
'end': instance.end,
'year': instance.year,
'stateCode': instance.stateCode,
'name': instance.name,
'slug': instance.slug,
};

View File

@@ -0,0 +1,13 @@
import 'package:dio/dio.dart';
import '../../../basis/dataloader/holiday_data_loader.dart';
import '../../../infrastructure/dataLoader/data_loader.dart';
import '../bloc/holidays_state.dart';
class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> {
@override
List<Holiday> assemble(DataLoaderResult data) => data.asListOfMaps().map(Holiday.fromJson).toList();
@override
Future<Response<String>> fetch() => dio.get('/holidays/HE');
}

View File

@@ -0,0 +1,7 @@
import '../../../infrastructure/repository/repository.dart';
import '../bloc/holidays_state.dart';
import '../dataProvider/holidays_get_holidays.dart';
class HolidaysRepository extends Repository<HolidaysState> {
Future<List<Holiday>> getHolidays() => HolidaysGetHolidays().run();
}

View File

@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import '../../../../../widget/animatedTime.dart';
import '../../../../../widget/list_view_util.dart';
import '../../../../../widget/centeredLeading.dart';
import '../../../../../widget/debug/debugTile.dart';
import '../../../../../widget/string_extensions.dart';
import '../../../infrastructure/loadableState/loadable_state.dart';
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
import '../bloc/holidays_bloc.dart';
import '../bloc/holidays_event.dart';
import '../bloc/holidays_state.dart';
class HolidaysView extends StatelessWidget {
const HolidaysView({super.key});
@override
Widget build(BuildContext context) => BlocModule<HolidaysBloc, LoadableState<HolidaysState>>(
create: (context) => HolidaysBloc(),
autoRebuild: true,
child: (context, bloc, state) {
void showDisclaimer() {
showDialog(context: context, builder: (context) => AlertDialog(
title: const Text('Richtigkeit und Bereitstellung der Daten'),
content: const Text(''
'Sämtliche Datumsangaben sind ohne Gewähr.\n'
'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n'
'Die Daten stammen von https://ferien-api.de/'),
actions: [
TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()),
],
));
}
return Scaffold(
appBar: AppBar(
title: const Text('Schulferien in Hessen'),
actions: [
IconButton(
icon: const Icon(Icons.info_outline),
onPressed: showDisclaimer,
),
PopupMenuButton<bool>(
initialValue: bloc.showPastHolidays(),
icon: const Icon(Icons.history),
itemBuilder: (context) => [true, false].map((e) => PopupMenuItem<bool>(
value: e,
enabled: e != bloc.showPastHolidays(),
child: Row(
children: [
Icon(e ? Icons.history_outlined : Icons.history_toggle_off_outlined, color: Theme.of(context).colorScheme.onSurface),
const SizedBox(width: 15),
Text(e ? 'Alle anzeigen' : 'Nur zukünftige anzeigen')
],
)
)).toList(),
onSelected: (e) => bloc.add(SetPastHolidaysVisible(e)),
),
],
),
body: LoadableStateConsumer<HolidaysBloc, HolidaysState>(
onLoad: (state) {
if(state.showDisclaimer) showDisclaimer();
bloc.add(DisclaimerDismissed());
},
child: (state, loading) => ListViewUtil.fromList<Holiday>(bloc.getHolidays(), (holiday) {
var holidayType = holiday.name.split(' ').first.capitalize();
String formatDate(String date) => Jiffy.parse(date).format(pattern: 'dd.MM.yyyy');
String getYear(String date, {String format = 'yyyy'}) => Jiffy.parse(date).format(pattern: format);
String getHolidayYear(String startDate, String endDate) => getYear(startDate) == getYear(endDate)
? getYear(startDate)
: '${getYear(startDate)}/${getYear(endDate, format: 'yy')}';
return ListTile(
leading: const CenteredLeading(Icon(Icons.calendar_month)),
title: Text('$holidayType ${getHolidayYear(holiday.start, holiday.end)}'),
subtitle: Text('${formatDate(holiday.start)} - ${formatDate(holiday.end)}'),
onTap: () => showDialog(context: context, builder: (context) => SimpleDialog(
title: Text('$holidayType ${holiday.year} in Hessen'),
children: [
ListTile(
leading: const CenteredLeading(Icon(Icons.signpost_outlined)),
title: Text(holiday.name.capitalize()),
subtitle: Text(holiday.slug.capitalize()),
),
ListTile(
leading: const Icon(Icons.date_range_outlined),
title: Text('vom ${formatDate(holiday.start)}'),
),
ListTile(
leading: const Icon(Icons.date_range_outlined),
title: Text('bis zum ${formatDate(holiday.end)}'),
),
Visibility(
visible: !DateTime.parse(holiday.start).difference(DateTime.now()).isNegative,
replacement: ListTile(
leading: const CenteredLeading(Icon(Icons.content_paste_search_outlined)),
title: Text(Jiffy.parse(holiday.start).fromNow()),
),
child: ListTile(
leading: const CenteredLeading(Icon(Icons.timer_outlined)),
title: AnimatedTime(callback: () => DateTime.parse(holiday.start).difference(DateTime.now())),
subtitle: Text(Jiffy.parse(holiday.start).fromNow()),
),
),
DebugTile(context).jsonData(holiday.toJson()),
],
)),
trailing: const Icon(Icons.arrow_right),
);
}),
),
);
},
);
}

View File

@@ -8,7 +8,7 @@ class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, Mar
@override @override
Future<void> gatherData() async { Future<void> gatherData() async {
var messages = await repo.getMessages(); var messages = await repo.getMessages();
add(Emit((state) => state.copyWith(messageList: messages))); add(DataGathered((state) => state.copyWith(messageList: messages)));
} }
@override @override

View File

@@ -22,8 +22,12 @@ MarianumMessageState _$MarianumMessageStateFromJson(Map<String, dynamic> json) {
mixin _$MarianumMessageState { mixin _$MarianumMessageState {
MarianumMessageList get messageList => throw _privateConstructorUsedError; MarianumMessageList get messageList => throw _privateConstructorUsedError;
/// Serializes this MarianumMessageState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MarianumMessageStateCopyWith<MarianumMessageState> get copyWith => $MarianumMessageStateCopyWith<MarianumMessageState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -50,6 +54,8 @@ class _$MarianumMessageStateCopyWithImpl<$Res,
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -63,6 +69,8 @@ class _$MarianumMessageStateCopyWithImpl<$Res,
) as $Val); ) as $Val);
} }
/// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$MarianumMessageListCopyWith<$Res> get messageList { $MarianumMessageListCopyWith<$Res> get messageList {
@@ -94,6 +102,8 @@ class __$$MarianumMessageStateImplCopyWithImpl<$Res>
$Res Function(_$MarianumMessageStateImpl) _then) $Res Function(_$MarianumMessageStateImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -133,11 +143,13 @@ class _$MarianumMessageStateImpl implements _MarianumMessageState {
other.messageList == messageList)); other.messageList == messageList));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, messageList); int get hashCode => Object.hash(runtimeType, messageList);
@JsonKey(ignore: true) /// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl> _$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
@@ -163,8 +175,11 @@ abstract class _MarianumMessageState implements MarianumMessageState {
@override @override
MarianumMessageList get messageList; MarianumMessageList get messageList;
/// Create a copy of MarianumMessageState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl> _$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }
@@ -178,8 +193,12 @@ mixin _$MarianumMessageList {
String get base => throw _privateConstructorUsedError; String get base => throw _privateConstructorUsedError;
List<MarianumMessage> get messages => throw _privateConstructorUsedError; List<MarianumMessage> get messages => throw _privateConstructorUsedError;
/// Serializes this MarianumMessageList to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of MarianumMessageList
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MarianumMessageListCopyWith<MarianumMessageList> get copyWith => $MarianumMessageListCopyWith<MarianumMessageList> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -203,6 +222,8 @@ class _$MarianumMessageListCopyWithImpl<$Res, $Val extends MarianumMessageList>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of MarianumMessageList
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -241,6 +262,8 @@ class __$$MarianumMessageListImplCopyWithImpl<$Res>
$Res Function(_$MarianumMessageListImpl) _then) $Res Function(_$MarianumMessageListImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of MarianumMessageList
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -294,12 +317,14 @@ class _$MarianumMessageListImpl implements _MarianumMessageList {
const DeepCollectionEquality().equals(other._messages, _messages)); const DeepCollectionEquality().equals(other._messages, _messages));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, base, const DeepCollectionEquality().hash(_messages)); runtimeType, base, const DeepCollectionEquality().hash(_messages));
@JsonKey(ignore: true) /// Create a copy of MarianumMessageList
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith => _$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith =>
@@ -327,8 +352,11 @@ abstract class _MarianumMessageList implements MarianumMessageList {
String get base; String get base;
@override @override
List<MarianumMessage> get messages; List<MarianumMessage> get messages;
/// Create a copy of MarianumMessageList
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith => _$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -343,8 +371,12 @@ mixin _$MarianumMessage {
String get date => throw _privateConstructorUsedError; String get date => throw _privateConstructorUsedError;
String get url => throw _privateConstructorUsedError; String get url => throw _privateConstructorUsedError;
/// Serializes this MarianumMessage to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of MarianumMessage
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$MarianumMessageCopyWith<MarianumMessage> get copyWith => $MarianumMessageCopyWith<MarianumMessage> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@@ -368,6 +400,8 @@ class _$MarianumMessageCopyWithImpl<$Res, $Val extends MarianumMessage>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of MarianumMessage
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -411,6 +445,8 @@ class __$$MarianumMessageImplCopyWithImpl<$Res>
_$MarianumMessageImpl _value, $Res Function(_$MarianumMessageImpl) _then) _$MarianumMessageImpl _value, $Res Function(_$MarianumMessageImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of MarianumMessage
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@@ -466,11 +502,13 @@ class _$MarianumMessageImpl implements _MarianumMessage {
(identical(other.url, url) || other.url == url)); (identical(other.url, url) || other.url == url));
} }
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType, name, date, url); int get hashCode => Object.hash(runtimeType, name, date, url);
@JsonKey(ignore: true) /// Create a copy of MarianumMessage
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith => _$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith =>
@@ -500,8 +538,11 @@ abstract class _MarianumMessage implements MarianumMessage {
String get date; String get date;
@override @override
String get url; String get url;
/// Create a copy of MarianumMessage
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(ignore: true) @JsonKey(includeFromJson: false, includeToJson: false)
_$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith => _$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@@ -1,7 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../../../infrastructure/dataLoader/data_loader.dart'; import '../../../infrastructure/dataLoader/data_loader.dart';
import '../../../infrastructure/dataLoader/mhsl_data_loader.dart'; import '../../../basis/dataloader/mhsl_data_loader.dart';
import '../bloc/marianum_message_state.dart'; import '../bloc/marianum_message_state.dart';
class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> { class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> {

View File

@@ -4,6 +4,7 @@ import 'package:json_annotation/json_annotation.dart';
import '../devTools/devToolsSettings.dart'; import '../devTools/devToolsSettings.dart';
import '../file/fileSettings.dart'; import '../file/fileSettings.dart';
import '../fileView/fileViewSettings.dart'; import '../fileView/fileViewSettings.dart';
import '../general/modulesSettings.dart';
import '../holidays/holidaysSettings.dart'; import '../holidays/holidaysSettings.dart';
import '../notification/notificationSettings.dart'; import '../notification/notificationSettings.dart';
import '../talk/talkSettings.dart'; import '../talk/talkSettings.dart';
@@ -20,6 +21,7 @@ class Settings {
ThemeMode appTheme; ThemeMode appTheme;
bool devToolsEnabled; bool devToolsEnabled;
ModulesSettings modulesSettings;
TimetableSettings timetableSettings; TimetableSettings timetableSettings;
TalkSettings talkSettings; TalkSettings talkSettings;
FileSettings fileSettings; FileSettings fileSettings;
@@ -31,6 +33,7 @@ class Settings {
Settings({ Settings({
required this.appTheme, required this.appTheme,
required this.devToolsEnabled, required this.devToolsEnabled,
required this.modulesSettings,
required this.timetableSettings, required this.timetableSettings,
required this.talkSettings, required this.talkSettings,
required this.fileSettings, required this.fileSettings,

View File

@@ -9,6 +9,8 @@ part of 'settings.dart';
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings( Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
appTheme: Settings._themeFromJson(json['appTheme'] as String), appTheme: Settings._themeFromJson(json['appTheme'] as String),
devToolsEnabled: json['devToolsEnabled'] as bool, devToolsEnabled: json['devToolsEnabled'] as bool,
modulesSettings: ModulesSettings.fromJson(
json['modulesSettings'] as Map<String, dynamic>),
timetableSettings: TimetableSettings.fromJson( timetableSettings: TimetableSettings.fromJson(
json['timetableSettings'] as Map<String, dynamic>), json['timetableSettings'] as Map<String, dynamic>),
talkSettings: talkSettings:
@@ -28,6 +30,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'appTheme': Settings._themeToJson(instance.appTheme), 'appTheme': Settings._themeToJson(instance.appTheme),
'devToolsEnabled': instance.devToolsEnabled, 'devToolsEnabled': instance.devToolsEnabled,
'modulesSettings': instance.modulesSettings.toJson(),
'timetableSettings': instance.timetableSettings.toJson(), 'timetableSettings': instance.timetableSettings.toJson(),
'talkSettings': instance.talkSettings.toJson(), 'talkSettings': instance.talkSettings.toJson(),
'fileSettings': instance.fileSettings.toJson(), 'fileSettings': instance.fileSettings.toJson(),

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:easy_debounce/easy_debounce.dart'; import 'package:easy_debounce/easy_debounce.dart';
@@ -13,6 +14,8 @@ class SettingsProvider extends ChangeNotifier {
late SharedPreferences _storage; late SharedPreferences _storage;
late Settings _settings = DefaultSettings.get(); late Settings _settings = DefaultSettings.get();
final Completer<void> _populated = Completer();
Settings val({bool write = false}) { Settings val({bool write = false}) {
if(write) { if(write) {
notifyListeners(); notifyListeners();
@@ -56,6 +59,7 @@ class SettingsProvider extends ChangeNotifier {
} }
notifyListeners(); notifyListeners();
_populated.complete();
} }
Future<void> update() async { Future<void> update() async {
@@ -77,4 +81,8 @@ class SettingsProvider extends ChangeNotifier {
return mergedMap; return mergedMap;
} }
Future<void> waitForPopulation() async {
await _populated.future;
}
} }

View File

@@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../state/app/modules/app_modules.dart';
part 'modulesSettings.g.dart';
@JsonSerializable()
class ModulesSettings {
List<Modules> moduleOrder;
List<Modules> hiddenModules;
ModulesSettings({
required this.moduleOrder,
required this.hiddenModules
});
factory ModulesSettings.fromJson(Map<String, dynamic> json) => _$ModulesSettingsFromJson(json);
Map<String, dynamic> toJson() => _$ModulesSettingsToJson(this);
}

View File

@@ -0,0 +1,35 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'modulesSettings.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ModulesSettings _$ModulesSettingsFromJson(Map<String, dynamic> json) =>
ModulesSettings(
moduleOrder: (json['moduleOrder'] as List<dynamic>)
.map((e) => $enumDecode(_$ModulesEnumMap, e))
.toList(),
hiddenModules: (json['hiddenModules'] as List<dynamic>)
.map((e) => $enumDecode(_$ModulesEnumMap, e))
.toList(),
);
Map<String, dynamic> _$ModulesSettingsToJson(ModulesSettings instance) =>
<String, dynamic>{
'moduleOrder':
instance.moduleOrder.map((e) => _$ModulesEnumMap[e]!).toList(),
'hiddenModules':
instance.hiddenModules.map((e) => _$ModulesEnumMap[e]!).toList(),
};
const _$ModulesEnumMap = {
Modules.timetable: 'timetable',
Modules.talk: 'talk',
Modules.files: 'files',
Modules.marianumMessage: 'marianumMessage',
Modules.roomPlan: 'roomPlan',
Modules.gradeAveragesCalculator: 'gradeAveragesCalculator',
Modules.holidays: 'holidays',
};

View File

@@ -1,12 +1,18 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../view/pages/timetable/timetableNameMode.dart';
part 'timetableSettings.g.dart'; part 'timetableSettings.g.dart';
@JsonSerializable() @JsonSerializable()
class TimetableSettings { class TimetableSettings {
bool connectDoubleLessons; bool connectDoubleLessons;
TimetableNameMode timetableNameMode;
TimetableSettings({required this.connectDoubleLessons}); TimetableSettings({
required this.connectDoubleLessons,
required this.timetableNameMode
});
factory TimetableSettings.fromJson(Map<String, dynamic> json) => _$TimetableSettingsFromJson(json); factory TimetableSettings.fromJson(Map<String, dynamic> json) => _$TimetableSettingsFromJson(json);
Map<String, dynamic> toJson() => _$TimetableSettingsToJson(this); Map<String, dynamic> toJson() => _$TimetableSettingsToJson(this);

View File

@@ -9,9 +9,19 @@ part of 'timetableSettings.dart';
TimetableSettings _$TimetableSettingsFromJson(Map<String, dynamic> json) => TimetableSettings _$TimetableSettingsFromJson(Map<String, dynamic> json) =>
TimetableSettings( TimetableSettings(
connectDoubleLessons: json['connectDoubleLessons'] as bool, connectDoubleLessons: json['connectDoubleLessons'] as bool,
timetableNameMode:
$enumDecode(_$TimetableNameModeEnumMap, json['timetableNameMode']),
); );
Map<String, dynamic> _$TimetableSettingsToJson(TimetableSettings instance) => Map<String, dynamic> _$TimetableSettingsToJson(TimetableSettings instance) =>
<String, dynamic>{ <String, dynamic>{
'connectDoubleLessons': instance.connectDoubleLessons, 'connectDoubleLessons': instance.connectDoubleLessons,
'timetableNameMode':
_$TimetableNameModeEnumMap[instance.timetableNameMode]!,
};
const _$TimetableNameModeEnumMap = {
TimetableNameMode.name: 'name',
TimetableNameMode.longName: 'longName',
TimetableNameMode.alternateName: 'alternateName',
}; };

View File

@@ -1,26 +1,21 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../widget/dropdownDisplay.dart';
class AppTheme { class AppTheme {
static ThemeModeDisplay getDisplayOptions(ThemeMode theme) { static DropdownDisplay getDisplayOptions(ThemeMode theme) {
switch(theme) { switch(theme) {
case ThemeMode.system: case ThemeMode.system:
return ThemeModeDisplay(icon: Icons.auto_fix_high_outlined, displayName: 'Systemvorgabe'); return DropdownDisplay(icon: Icons.auto_fix_high_outlined, displayName: 'Systemvorgabe');
case ThemeMode.light: case ThemeMode.light:
return ThemeModeDisplay(icon: Icons.wb_sunny_outlined, displayName: 'Hell'); return DropdownDisplay(icon: Icons.wb_sunny_outlined, displayName: 'Hell');
case ThemeMode.dark: case ThemeMode.dark:
return ThemeModeDisplay(icon: Icons.dark_mode_outlined, displayName: 'Dunkel'); return DropdownDisplay(icon: Icons.dark_mode_outlined, displayName: 'Dunkel');
} }
} }
static bool isDarkMode(BuildContext context) => Theme.of(context).brightness == Brightness.dark; static bool isDarkMode(BuildContext context) => Theme.of(context).brightness == Brightness.dark;
} }
class ThemeModeDisplay {
final IconData icon;
final String displayName;
ThemeModeDisplay({required this.icon, required this.displayName});
}

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