Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada4d6a77c | ||
|
|
cd88cc75fc | ||
|
|
279b973e89 | ||
|
|
b90cda62da | ||
|
|
a79b376bb3 | ||
|
|
eddd294a34 | ||
|
|
c455bb1cae | ||
|
|
aca86c506a | ||
|
|
2664378b17 | ||
|
|
ee1240f71a |
No files matched your search
@@ -4,4 +4,5 @@ targets:
|
||||
json_serializable:
|
||||
options:
|
||||
explicit_to_json: false
|
||||
checked: true
|
||||
generic_argument_factories: true
|
||||
+2
-8
@@ -12,8 +12,6 @@ PODS:
|
||||
- PhoneNumberKit/PhoneNumberKitCore (3.7.11)
|
||||
- PhoneNumberKit/UIKit (3.7.11):
|
||||
- PhoneNumberKit/PhoneNumberKitCore
|
||||
- workmanager_apple (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- eraser (from `.symlinks/plugins/eraser/ios`)
|
||||
@@ -21,7 +19,6 @@ DEPENDENCIES:
|
||||
- flutter_app_badge (from `.symlinks/plugins/flutter_app_badge/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- PhoneNumberKit (~> 3.7.6)
|
||||
- workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -36,17 +33,14 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_app_badge/ios"
|
||||
open_filex:
|
||||
:path: ".symlinks/plugins/open_filex/ios"
|
||||
workmanager_apple:
|
||||
:path: ".symlinks/plugins/workmanager_apple/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
eraser: 83a4b06985f3702aa3d8dec816f9693266012937
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
Flutter: 71a624a5bc0c04062bf19101d501e466baf2fb47
|
||||
flutter_app_badge: ca742dd659a157c1090ef7cd881cb78f48f3bcdf
|
||||
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
||||
PhoneNumberKit: 9ff0c5ae9fe4770193b68a3d3e6c938fe976788c
|
||||
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
|
||||
|
||||
PODFILE CHECKSUM: 087d168982f24fb137e2d46f893b771b4b9955c6
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
COCOAPODS: 1.17.0
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 70;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -77,19 +77,19 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
12B96C78930441C73F123636 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = Info.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
12B96C78930441C73F123636 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
17E2EE012C4361AC05DCA4C9 /* NotificationServiceExtension-Release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Release.xcconfig"; path = "Flutter/NotificationServiceExtension-Release.xcconfig"; sourceTree = "<group>"; };
|
||||
2960F029246C39E2A03F6D87 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NotificationService.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
2960F029246C39E2A03F6D87 /* NotificationService.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
3321F8052FB1C00C0011C712 /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
36C6C71327C68B43F522F5B2 /* NotificationServiceExtension-Debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "NotificationServiceExtension-Debug.xcconfig"; path = "Flutter/NotificationServiceExtension-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
4509EC31CB08BA9BF367AF6C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
509DCCD474353408FE5806C5 /* PEM.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PEM.swift; path = PEM.swift; sourceTree = "<group>"; };
|
||||
5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; name = NotificationServiceExtension.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = "<group>"; };
|
||||
509DCCD474353408FE5806C5 /* PEM.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PEM.swift; sourceTree = "<group>"; };
|
||||
5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = "<group>"; };
|
||||
60E1803A3FB28FCC6F435E99 /* Pods-Share Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.release.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
64801C012A9112D500E8B558 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@@ -222,7 +222,6 @@
|
||||
12B96C78930441C73F123636 /* Info.plist */,
|
||||
5C2F4C79DD573778092882AB /* NotificationServiceExtension.entitlements */,
|
||||
);
|
||||
name = NotificationServiceExtension;
|
||||
path = NotificationServiceExtension;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "3e33dd27dd4c69bd81c7c81fe61d8ccf58846902",
|
||||
"version" : "11.3.1"
|
||||
"revision" : "97f7d74dd0e8f3d0fe5fc75cc8957aa73bdc948a",
|
||||
"version" : "11.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,8 +50,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "42e81d245e30e49ea6a5830cf2842d44a1591270",
|
||||
"version" : "12.15.0"
|
||||
"revision" : "27eaab3918e0bf78711cf1abf240577176326432",
|
||||
"version" : "12.19.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "dc39082d8881109d35b94b1c122164c0e8d08a55",
|
||||
"version" : "3.6.1"
|
||||
"revision" : "0208e2681ec81f8a9c81084696f60fcce26cb7ee",
|
||||
"version" : "3.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -68,8 +68,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "144855f40d8668927f256a3045f7fdc4c3f4338b",
|
||||
"version" : "12.15.0"
|
||||
"revision" : "b8455dd2ff55cc269fda7a4f9eac6c1e3588c53f",
|
||||
"version" : "12.19.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -77,8 +77,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||
"state" : {
|
||||
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
|
||||
"version" : "10.1.0"
|
||||
"revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974",
|
||||
"version" : "10.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,8 +86,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "9f183ae842be978784f2963a343682e0c46d8fb3",
|
||||
"version" : "8.1.2"
|
||||
"revision" : "92c8f6dc3ac375d6febdfcb3db68bc3d10633db3",
|
||||
"version" : "8.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -104,8 +104,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/gtm-session-fetcher.git",
|
||||
"state" : {
|
||||
"revision" : "c0ac7575d70050c2973ba2318bd5af47f8e8153a",
|
||||
"version" : "5.3.0"
|
||||
"revision" : "724a52eea6329b7e12d3ad8300d76ca9f3895fcc",
|
||||
"version" : "5.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "42e81d245e30e49ea6a5830cf2842d44a1591270",
|
||||
"version" : "12.15.0"
|
||||
"revision" : "27eaab3918e0bf78711cf1abf240577176326432",
|
||||
"version" : "12.19.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -59,8 +59,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "9bfcc6cf435b2e7c5562c1900b8680c594fa9a64",
|
||||
"version" : "3.6.0"
|
||||
"revision" : "0208e2681ec81f8a9c81084696f60fcce26cb7ee",
|
||||
"version" : "3.7.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -68,8 +68,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "144855f40d8668927f256a3045f7fdc4c3f4338b",
|
||||
"version" : "12.15.0"
|
||||
"revision" : "b8455dd2ff55cc269fda7a4f9eac6c1e3588c53f",
|
||||
"version" : "12.19.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,8 +86,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||
"state" : {
|
||||
"revision" : "c46e5f8b7c23265f17c24ca7f9fa1b13ded7a822",
|
||||
"version" : "8.1.1"
|
||||
"revision" : "92c8f6dc3ac375d6febdfcb3db68bc3d10633db3",
|
||||
"version" : "8.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+86
-86
@@ -1,94 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Marianum Fulda</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>client</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Um Fotos direkt aus der App aufnehmen und teilen zu können wird Zugriff auf die Kamera benötigt.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>eu.mhsl.marianum.widget.refresh</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Marianum Fulda</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>client</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>eu.mhsl.marianum.widget.refresh</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Um Fotos direkt aus der App aufnehmen und teilen zu können wird Zugriff auf die Kamera benötigt.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -7,4 +7,9 @@ abstract class ApiResponse {
|
||||
|
||||
@JsonKey(includeIfNull: false)
|
||||
Map<String, String>? headers;
|
||||
|
||||
/// Entries the decoder skipped (see `decodeListLenient`), reported by the
|
||||
/// API layer once the response is back on the main isolate.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
List<String> parseWarnings = const [];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart';
|
||||
|
||||
import '../../model/endpoint_data.dart';
|
||||
import '../errors/network_exception.dart';
|
||||
import 'demo_mode.dart';
|
||||
|
||||
/// Demo sessions carry made-up credentials. Every request they sent to the
|
||||
/// real Nextcloud would count as a failed login there and, in bulk, trip the
|
||||
/// server's brute-force throttle for the whole IP (HTTP 429 for everyone
|
||||
/// behind it). Installed app-wide via [http.runWithClient].
|
||||
class DemoHttpClient extends http.BaseClient {
|
||||
final http.Client _inner = IOClient();
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) {
|
||||
if (DemoMode.active && isNextcloudHost(request.url)) {
|
||||
return Future.error(NetworkException.demoBlocked(request.url));
|
||||
}
|
||||
return _inner.send(request);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() => _inner.close();
|
||||
|
||||
static bool isNextcloudHost(Uri url) =>
|
||||
url.host == EndpointData().nextcloud().domain;
|
||||
}
|
||||
@@ -10,9 +10,7 @@ import '../api_error.dart';
|
||||
import '../http_errors.dart';
|
||||
import '../marianumcloud/talk/talk_error.dart';
|
||||
import 'app_exception.dart';
|
||||
import 'auth_exception.dart';
|
||||
import 'network_exception.dart';
|
||||
import 'not_found_exception.dart';
|
||||
import 'parse_exception.dart';
|
||||
import 'server_exception.dart';
|
||||
import 'talk_exception.dart';
|
||||
@@ -65,23 +63,15 @@ AppException _dynamiteToAppException(DynamiteApiException error) {
|
||||
final detail = preview.isEmpty
|
||||
? 'HTTP $status'
|
||||
: 'HTTP $status body=$preview';
|
||||
switch (status) {
|
||||
case 401:
|
||||
return AuthException.unauthorized(technicalDetails: detail);
|
||||
case 403:
|
||||
return AuthException.forbidden(technicalDetails: detail);
|
||||
case 404:
|
||||
return NotFoundException(technicalDetails: detail);
|
||||
case 429:
|
||||
return ServerException(
|
||||
statusCode: status,
|
||||
userMessage:
|
||||
'Zu viele Anfragen. Bitte warte einen Moment, bevor du es erneut versuchst.',
|
||||
technicalDetails: detail,
|
||||
);
|
||||
default:
|
||||
return ServerException(statusCode: status, technicalDetails: detail);
|
||||
if (status == 429) {
|
||||
return ServerException(
|
||||
statusCode: status,
|
||||
userMessage:
|
||||
'Zu viele Anfragen. Bitte warte einen Moment, bevor du es erneut versuchst.',
|
||||
technicalDetails: detail,
|
||||
);
|
||||
}
|
||||
return exceptionForStatus(status, detail);
|
||||
}
|
||||
|
||||
String errorToUserMessage(Object? error, {String fallback = _defaultFallback}) {
|
||||
|
||||
@@ -13,4 +13,10 @@ class NetworkException extends AppException {
|
||||
'Der Server hat zu lange gebraucht. Bitte versuche es erneut.',
|
||||
technicalDetails: technicalDetails,
|
||||
);
|
||||
|
||||
/// A request a demo session must never send (see `DemoHttpClient`).
|
||||
factory NetworkException.demoBlocked(Uri url) => NetworkException(
|
||||
userMessage: 'Im Demo-Modus nicht verfügbar.',
|
||||
technicalDetails: 'demo mode blocked $url',
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'errors/app_exception.dart';
|
||||
import 'errors/auth_exception.dart';
|
||||
import 'errors/network_exception.dart';
|
||||
import 'errors/not_found_exception.dart';
|
||||
@@ -42,12 +43,23 @@ String httpErrorDetail(String label, String body, int status) {
|
||||
: '$label -> HTTP $status body=$preview';
|
||||
}
|
||||
|
||||
/// Throws the [AppException] matching a non-2xx HTTP [status], carrying
|
||||
/// [detail] as technical details: 401/403 map to auth errors, 404 to
|
||||
/// not-found, everything else to a generic server error.
|
||||
Never throwForStatus(int status, String detail) {
|
||||
if (status == 401) throw AuthException.unauthorized(technicalDetails: detail);
|
||||
if (status == 403) throw AuthException.forbidden(technicalDetails: detail);
|
||||
if (status == 404) throw NotFoundException(technicalDetails: detail);
|
||||
throw ServerException(statusCode: status, technicalDetails: detail);
|
||||
/// The [AppException] matching a non-2xx HTTP [status], carrying [detail] as
|
||||
/// technical details: 401/403 map to auth errors, 404 to not-found, everything
|
||||
/// else to a generic server error.
|
||||
AppException exceptionForStatus(int status, String detail) => switch (status) {
|
||||
401 => AuthException.unauthorized(technicalDetails: detail),
|
||||
403 => AuthException.forbidden(technicalDetails: detail),
|
||||
404 => NotFoundException(technicalDetails: detail),
|
||||
_ => ServerException(statusCode: status, technicalDetails: detail),
|
||||
};
|
||||
|
||||
/// Throws [exceptionForStatus].
|
||||
Never throwForStatus(int status, String detail) =>
|
||||
throw exceptionForStatus(status, detail);
|
||||
|
||||
/// Throws [exceptionForStatus] unless [response] is 2xx.
|
||||
void ensureSuccess(String label, http.Response response) {
|
||||
final status = response.statusCode;
|
||||
if (status >= 200 && status < 300) return;
|
||||
throwForStatus(status, httpErrorDetail(label, response.body, status));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
/// Decodes server lists element by element so one entry the model cannot map
|
||||
/// (a field the server dropped, an enum value it added) is skipped and
|
||||
/// recorded instead of failing the whole response and with it the screen.
|
||||
///
|
||||
/// Skipped entries are collected per isolate; the API layer drains them with
|
||||
/// [drainDecodeWarnings] and reports them, so a drift in the server's format
|
||||
/// still reaches us without taking the feature down for the user.
|
||||
final List<String> _warnings = [];
|
||||
|
||||
List<T> decodeListLenient<T>(
|
||||
Object? raw,
|
||||
T Function(Map<String, dynamic>) fromJson, {
|
||||
required String what,
|
||||
}) {
|
||||
final items = <T>[];
|
||||
if (raw is! List<dynamic>) {
|
||||
throw FormatException(
|
||||
'expected a list of ${what}s, got ${raw.runtimeType}',
|
||||
);
|
||||
}
|
||||
for (final entry in raw) {
|
||||
try {
|
||||
items.add(fromJson(entry as Map<String, dynamic>));
|
||||
} on CheckedFromJsonException catch (e) {
|
||||
_warnings.add('$what: $e');
|
||||
} on TypeError catch (e) {
|
||||
_warnings.add('$what: $e');
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Set<T> decodeSetLenient<T>(
|
||||
Object? raw,
|
||||
T Function(Map<String, dynamic>) fromJson, {
|
||||
required String what,
|
||||
}) => decodeListLenient(raw, fromJson, what: what).toSet();
|
||||
|
||||
/// Returns and clears the entries skipped since the last call.
|
||||
List<String> drainDecodeWarnings() {
|
||||
final out = List<String>.of(_warnings);
|
||||
_warnings.clear();
|
||||
return out;
|
||||
}
|
||||
@@ -18,13 +18,7 @@ class DeleteAppPassword {
|
||||
NextcloudOcs.uri('core/apppassword'),
|
||||
// An explicit header may belong to an inactive account, so the active
|
||||
// session's headers must not be required then.
|
||||
headers: authorizationHeader == null
|
||||
? NextcloudOcs.headers()
|
||||
: {
|
||||
'Accept': 'application/json',
|
||||
'OCS-APIRequest': 'true',
|
||||
'Authorization': authorizationHeader,
|
||||
},
|
||||
headers: NextcloudOcs.headers(authorization: authorizationHeader),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,23 +28,17 @@ class GetAppPassword {
|
||||
label,
|
||||
() => _client.get(
|
||||
NextcloudOcs.uri('core/getapppassword'),
|
||||
headers: {
|
||||
...NextcloudOcs.headers(),
|
||||
// Deliberately NOT the shared Authorization value: that one prefers
|
||||
// the app password, but an app password cannot mint another one —
|
||||
// this endpoint requires the real password.
|
||||
'Authorization': SessionManager()
|
||||
// Deliberately NOT the shared Authorization value: that one prefers
|
||||
// the app password, but an app password cannot mint another one —
|
||||
// this endpoint requires the real password.
|
||||
headers: NextcloudOcs.headers(
|
||||
authorization: SessionManager()
|
||||
.requireNextcloud()
|
||||
.realPasswordBasicAuthHeader,
|
||||
},
|
||||
),
|
||||
),
|
||||
))!;
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throwForStatus(
|
||||
response.statusCode,
|
||||
httpErrorDetail(label, response.body, response.statusCode),
|
||||
);
|
||||
}
|
||||
ensureSuccess(label, response);
|
||||
final json = jsonDecode(utf8.decode(response.bodyBytes));
|
||||
final data = (json as Map)['ocs']?['data'];
|
||||
final appPassword = data is Map ? data['apppassword'] as String? : null;
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:http/http.dart' as http;
|
||||
import '../../../model/endpoint_data.dart';
|
||||
import '../../errors/server_exception.dart';
|
||||
import '../../http_errors.dart';
|
||||
import '../../json_lenient.dart';
|
||||
import '../../marianumconnect/queries/report_client_error/client_error_reporter.dart';
|
||||
import '../nextcloud_ocs.dart';
|
||||
import 'autocomplete_response.dart';
|
||||
|
||||
@@ -43,6 +45,13 @@ class AutocompleteApi {
|
||||
technicalDetails: 'core/autocomplete/get: ${response.body}',
|
||||
);
|
||||
}
|
||||
return AutocompleteResponse.fromJson(NextcloudOcs.decode(response.body));
|
||||
final result = AutocompleteResponse.fromJson(
|
||||
NextcloudOcs.decode(response.body),
|
||||
);
|
||||
ClientErrorReporter.reportDecodeWarnings(
|
||||
'Autocomplete',
|
||||
drainDecodeWarnings(),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../json_lenient.dart';
|
||||
import '../files_sharing/queries/share/share.dart';
|
||||
|
||||
part 'autocomplete_response.g.dart';
|
||||
@@ -14,6 +15,7 @@ int shareTypeFromSource(String? source) {
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class AutocompleteResponse {
|
||||
@JsonKey(fromJson: _entriesFromJson)
|
||||
List<AutocompleteResponseObject> data;
|
||||
|
||||
AutocompleteResponse(this.data);
|
||||
@@ -23,15 +25,31 @@ class AutocompleteResponse {
|
||||
Map<String, dynamic> toJson() => _$AutocompleteResponseToJson(this);
|
||||
}
|
||||
|
||||
List<AutocompleteResponseObject> _entriesFromJson(Object? raw) =>
|
||||
decodeListLenient(
|
||||
raw,
|
||||
AutocompleteResponseObject.fromJson,
|
||||
what: 'autocomplete entry',
|
||||
);
|
||||
|
||||
/// The server sends a user-status object for users that have one and an empty
|
||||
/// string otherwise; only the status string itself is kept.
|
||||
String? _statusFromJson(Object? raw) => switch (raw) {
|
||||
Map<String, dynamic> status => status['status']?.toString(),
|
||||
String status when status.isNotEmpty => status,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@JsonSerializable()
|
||||
class AutocompleteResponseObject {
|
||||
String id;
|
||||
String label;
|
||||
String? icon;
|
||||
String? source;
|
||||
@JsonKey(fromJson: _statusFromJson)
|
||||
String? status;
|
||||
String? subline;
|
||||
String? shareWithDisplayNameUniqe;
|
||||
String? shareWithDisplayNameUnique;
|
||||
|
||||
AutocompleteResponseObject(
|
||||
this.id,
|
||||
@@ -40,7 +58,7 @@ class AutocompleteResponseObject {
|
||||
this.source,
|
||||
this.status,
|
||||
this.subline,
|
||||
this.shareWithDisplayNameUniqe,
|
||||
this.shareWithDisplayNameUnique,
|
||||
);
|
||||
|
||||
factory AutocompleteResponseObject.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -8,13 +8,12 @@ part of 'autocomplete_response.dart';
|
||||
|
||||
AutocompleteResponse _$AutocompleteResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => AutocompleteResponse(
|
||||
(json['data'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => AutocompleteResponseObject.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
) => $checkedCreate('AutocompleteResponse', json, ($checkedConvert) {
|
||||
final val = AutocompleteResponse(
|
||||
$checkedConvert('data', (v) => _entriesFromJson(v)),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$AutocompleteResponseToJson(
|
||||
AutocompleteResponse instance,
|
||||
@@ -22,15 +21,18 @@ Map<String, dynamic> _$AutocompleteResponseToJson(
|
||||
|
||||
AutocompleteResponseObject _$AutocompleteResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => AutocompleteResponseObject(
|
||||
json['id'] as String,
|
||||
json['label'] as String,
|
||||
json['icon'] as String?,
|
||||
json['source'] as String?,
|
||||
json['status'] as String?,
|
||||
json['subline'] as String?,
|
||||
json['shareWithDisplayNameUniqe'] as String?,
|
||||
);
|
||||
) => $checkedCreate('AutocompleteResponseObject', json, ($checkedConvert) {
|
||||
final val = AutocompleteResponseObject(
|
||||
$checkedConvert('id', (v) => v as String),
|
||||
$checkedConvert('label', (v) => v as String),
|
||||
$checkedConvert('icon', (v) => v as String?),
|
||||
$checkedConvert('source', (v) => v as String?),
|
||||
$checkedConvert('status', (v) => _statusFromJson(v)),
|
||||
$checkedConvert('subline', (v) => v as String?),
|
||||
$checkedConvert('shareWithDisplayNameUnique', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$AutocompleteResponseObjectToJson(
|
||||
AutocompleteResponseObject instance,
|
||||
@@ -41,5 +43,5 @@ Map<String, dynamic> _$AutocompleteResponseObjectToJson(
|
||||
'source': instance.source,
|
||||
'status': instance.status,
|
||||
'subline': instance.subline,
|
||||
'shareWithDisplayNameUniqe': instance.shareWithDisplayNameUniqe,
|
||||
'shareWithDisplayNameUnique': instance.shareWithDisplayNameUnique,
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -96,9 +95,8 @@ class GetUserInfo {
|
||||
uri,
|
||||
);
|
||||
try {
|
||||
final root = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final data =
|
||||
(root['ocs'] as Map<String, dynamic>)['data'] as Map<String, dynamic>;
|
||||
NextcloudOcs.decode(response.body)['data'] as Map<String, dynamic>;
|
||||
return CloudUserInfo(
|
||||
userId: data['id'] as String,
|
||||
displayName: (data['displayname'] as String?) ?? '',
|
||||
|
||||
@@ -8,16 +8,19 @@ part of 'file_sharing_api_params.dart';
|
||||
|
||||
FileSharingApiParams _$FileSharingApiParamsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => FileSharingApiParams(
|
||||
shareType: (json['shareType'] as num).toInt(),
|
||||
shareWith: json['shareWith'] as String,
|
||||
path: json['path'] as String,
|
||||
referenceId: json['referenceId'] as String?,
|
||||
talkMetaData: json['talkMetaData'] as String?,
|
||||
permissions: (json['permissions'] as num?)?.toInt(),
|
||||
password: json['password'] as String?,
|
||||
expireDate: json['expireDate'] as String?,
|
||||
);
|
||||
) => $checkedCreate('FileSharingApiParams', json, ($checkedConvert) {
|
||||
final val = FileSharingApiParams(
|
||||
shareType: $checkedConvert('shareType', (v) => (v as num).toInt()),
|
||||
shareWith: $checkedConvert('shareWith', (v) => v as String),
|
||||
path: $checkedConvert('path', (v) => v as String),
|
||||
referenceId: $checkedConvert('referenceId', (v) => v as String?),
|
||||
talkMetaData: $checkedConvert('talkMetaData', (v) => v as String?),
|
||||
permissions: $checkedConvert('permissions', (v) => (v as num?)?.toInt()),
|
||||
password: $checkedConvert('password', (v) => v as String?),
|
||||
expireDate: $checkedConvert('expireDate', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$FileSharingApiParamsToJson(
|
||||
FileSharingApiParams instance,
|
||||
|
||||
@@ -29,12 +29,7 @@ class LoginFlowApi {
|
||||
headers: {'Accept': 'application/json', 'User-Agent': userAgent},
|
||||
),
|
||||
))!;
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throwForStatus(
|
||||
response.statusCode,
|
||||
httpErrorDetail(label, response.body, response.statusCode),
|
||||
);
|
||||
}
|
||||
ensureSuccess(label, response);
|
||||
return LoginFlowInit.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
);
|
||||
@@ -53,12 +48,7 @@ class LoginFlowApi {
|
||||
),
|
||||
))!;
|
||||
if (response.statusCode == 404) return null;
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throwForStatus(
|
||||
response.statusCode,
|
||||
httpErrorDetail(label, response.body, response.statusCode),
|
||||
);
|
||||
}
|
||||
ensureSuccess(label, response);
|
||||
return LoginFlowCredentials.fromJson(
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
@@ -13,10 +13,13 @@ class NextcloudOcs {
|
||||
static Map<String, dynamic> decode(String raw) =>
|
||||
(jsonDecode(raw) as Map<String, dynamic>)['ocs'] as Map<String, dynamic>;
|
||||
|
||||
static Map<String, String> headers() => {
|
||||
/// [authorization] overrides the active session's credentials, which are
|
||||
/// then not required.
|
||||
static Map<String, String> headers({String? authorization}) => {
|
||||
'Accept': 'application/json',
|
||||
'OCS-APIRequest': 'true',
|
||||
'Authorization': SessionManager().requireNextcloud().basicAuthHeader,
|
||||
'Authorization':
|
||||
authorization ?? SessionManager().requireNextcloud().basicAuthHeader,
|
||||
};
|
||||
|
||||
static Uri uri(String pathSuffix, {Map<String, dynamic>? queryParameters}) {
|
||||
|
||||
@@ -7,14 +7,20 @@ part of 'search_files_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
SearchFilesResponse _$SearchFilesResponseFromJson(Map<String, dynamic> json) =>
|
||||
SearchFilesResponse(
|
||||
name: json['name'] as String,
|
||||
isPaginated: json['isPaginated'] as bool,
|
||||
cursor: (json['cursor'] as num?)?.toInt(),
|
||||
entries: (json['entries'] as List<dynamic>)
|
||||
.map((e) => SearchFilesEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
$checkedCreate('SearchFilesResponse', json, ($checkedConvert) {
|
||||
final val = SearchFilesResponse(
|
||||
name: $checkedConvert('name', (v) => v as String),
|
||||
isPaginated: $checkedConvert('isPaginated', (v) => v as bool),
|
||||
cursor: $checkedConvert('cursor', (v) => (v as num?)?.toInt()),
|
||||
entries: $checkedConvert(
|
||||
'entries',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => SearchFilesEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$SearchFilesResponseToJson(
|
||||
SearchFilesResponse instance,
|
||||
@@ -26,13 +32,19 @@ Map<String, dynamic> _$SearchFilesResponseToJson(
|
||||
};
|
||||
|
||||
SearchFilesEntry _$SearchFilesEntryFromJson(Map<String, dynamic> json) =>
|
||||
SearchFilesEntry(
|
||||
title: json['title'] as String,
|
||||
subline: json['subline'] as String?,
|
||||
icon: json['icon'] as String?,
|
||||
resourceUrl: json['resourceUrl'] as String?,
|
||||
attributes: json['attributes'] as Map<String, dynamic>?,
|
||||
);
|
||||
$checkedCreate('SearchFilesEntry', json, ($checkedConvert) {
|
||||
final val = SearchFilesEntry(
|
||||
title: $checkedConvert('title', (v) => v as String),
|
||||
subline: $checkedConvert('subline', (v) => v as String?),
|
||||
icon: $checkedConvert('icon', (v) => v as String?),
|
||||
resourceUrl: $checkedConvert('resourceUrl', (v) => v as String?),
|
||||
attributes: $checkedConvert(
|
||||
'attributes',
|
||||
(v) => v as Map<String, dynamic>?,
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$SearchFilesEntryToJson(SearchFilesEntry instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../errors/server_exception.dart';
|
||||
import '../../../http_errors.dart';
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import 'get_chat_params.dart';
|
||||
import 'get_chat_response.dart';
|
||||
|
||||
/// GETs a chat page outside [TalkApi], which treats non-2xx as errors: here
|
||||
/// HTTP 304 (nothing new / no older messages) is a normal outcome and yields
|
||||
/// `null`.
|
||||
Future<GetChatResponse?> fetchChatPage({
|
||||
required String label,
|
||||
required String chatToken,
|
||||
required GetChatParams params,
|
||||
Duration? timeout,
|
||||
bool parseOffIsolate = false,
|
||||
}) async {
|
||||
final uri = NextcloudOcs.uri(
|
||||
'apps/spreed/api/v1/chat/$chatToken',
|
||||
queryParameters: params.toJson(),
|
||||
);
|
||||
final headers = NextcloudOcs.headers();
|
||||
|
||||
final response = (await sendGuarded('$label $uri', () {
|
||||
final request = http.get(uri, headers: headers);
|
||||
return timeout == null ? request : request.timeout(timeout);
|
||||
}))!;
|
||||
|
||||
final status = response.statusCode;
|
||||
if (status == 304) return null;
|
||||
if (status >= 200 && status < 300) {
|
||||
final parsed = parseOffIsolate
|
||||
? await compute(_parseChatResponse, response.body)
|
||||
: _parseChatResponse(response.body);
|
||||
return parsed..headers = response.headers;
|
||||
}
|
||||
throw ServerException(
|
||||
statusCode: status,
|
||||
technicalDetails: '$label $uri: HTTP $status',
|
||||
);
|
||||
}
|
||||
|
||||
GetChatResponse _parseChatResponse(String body) =>
|
||||
GetChatResponse.fromJson(NextcloudOcs.decode(body));
|
||||
@@ -1,6 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_chat_params.dart';
|
||||
@@ -16,11 +13,4 @@ class GetChat extends TalkApi<GetChatResponse> {
|
||||
@override
|
||||
GetChatResponse assemble(String raw) =>
|
||||
GetChatResponse.fromJson(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../errors/server_exception.dart';
|
||||
import '../../../http_errors.dart';
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import 'fetch_chat_page.dart';
|
||||
import 'get_chat_params.dart';
|
||||
import 'get_chat_response.dart';
|
||||
|
||||
/// Backwards-paging variant of GetChat (`lookIntoFuture=0` + `lastKnownMessageId`)
|
||||
/// that fetches the page of messages *older* than a given id. Bypasses [TalkApi]
|
||||
/// because that layer treats non-2xx as errors, and the server answers HTTP 304
|
||||
/// when there are no older messages left — a normal "start of chat" outcome here.
|
||||
/// `setReadMarker=off` so paging into history never moves the read cursor.
|
||||
/// that fetches the page of messages *older* than a given id. The server
|
||||
/// answers HTTP 304 when there are no older messages left — a normal "start of
|
||||
/// chat" outcome (see [fetchChatPage]). `setReadMarker=off` so paging into
|
||||
/// history never moves the read cursor.
|
||||
class GetChatHistory {
|
||||
final String chatToken;
|
||||
final int lastKnownMessageId;
|
||||
@@ -24,39 +19,18 @@ class GetChatHistory {
|
||||
});
|
||||
|
||||
/// Returns the older page, or `null` on HTTP 304 (no older messages).
|
||||
Future<GetChatResponse?> run() async {
|
||||
final params = GetChatParams(
|
||||
Future<GetChatResponse?> run() => fetchChatPage(
|
||||
label: 'GetChatHistory',
|
||||
chatToken: chatToken,
|
||||
params: GetChatParams(
|
||||
lookIntoFuture: GetChatParamsSwitch.off,
|
||||
lastKnownMessageId: lastKnownMessageId,
|
||||
includeLastKnown: GetChatParamsSwitch.off,
|
||||
setReadMarker: GetChatParamsSwitch.off,
|
||||
limit: limit,
|
||||
);
|
||||
final uri = NextcloudOcs.uri(
|
||||
'apps/spreed/api/v1/chat/$chatToken',
|
||||
queryParameters: params.toJson(),
|
||||
);
|
||||
final headers = NextcloudOcs.headers();
|
||||
|
||||
final response = (await sendGuarded(
|
||||
'GetChatHistory $uri',
|
||||
() => http.get(uri, headers: headers),
|
||||
))!;
|
||||
|
||||
final status = response.statusCode;
|
||||
if (status == 304) return null;
|
||||
if (status >= 200 && status < 300) {
|
||||
// A page holds up to 200 messages and lands while the user is scrolling;
|
||||
// decoding it on the UI isolate stalls the fling.
|
||||
final parsed = await compute(_parseChatResponse, response.body);
|
||||
return parsed..headers = response.headers;
|
||||
}
|
||||
throw ServerException(
|
||||
statusCode: status,
|
||||
technicalDetails: 'GetChatHistory $uri: HTTP $status',
|
||||
);
|
||||
}
|
||||
),
|
||||
// A page holds up to 200 messages and lands while the user is scrolling;
|
||||
// decoding it on the UI isolate stalls the fling.
|
||||
parseOffIsolate: true,
|
||||
);
|
||||
}
|
||||
|
||||
GetChatResponse _parseChatResponse(String body) =>
|
||||
GetChatResponse.fromJson(NextcloudOcs.decode(body));
|
||||
@@ -7,24 +7,33 @@ part of 'get_chat_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
GetChatParams _$GetChatParamsFromJson(Map<String, dynamic> json) =>
|
||||
GetChatParams(
|
||||
lookIntoFuture: $enumDecode(
|
||||
_$GetChatParamsSwitchEnumMap,
|
||||
json['lookIntoFuture'],
|
||||
),
|
||||
limit: (json['limit'] as num?)?.toInt(),
|
||||
lastKnownMessageId: (json['lastKnownMessageId'] as num?)?.toInt(),
|
||||
lastCommonReadId: (json['lastCommonReadId'] as num?)?.toInt(),
|
||||
timeout: (json['timeout'] as num?)?.toInt(),
|
||||
setReadMarker: $enumDecodeNullable(
|
||||
_$GetChatParamsSwitchEnumMap,
|
||||
json['setReadMarker'],
|
||||
),
|
||||
includeLastKnown: $enumDecodeNullable(
|
||||
_$GetChatParamsSwitchEnumMap,
|
||||
json['includeLastKnown'],
|
||||
),
|
||||
);
|
||||
$checkedCreate('GetChatParams', json, ($checkedConvert) {
|
||||
final val = GetChatParams(
|
||||
lookIntoFuture: $checkedConvert(
|
||||
'lookIntoFuture',
|
||||
(v) => $enumDecode(_$GetChatParamsSwitchEnumMap, v),
|
||||
),
|
||||
limit: $checkedConvert('limit', (v) => (v as num?)?.toInt()),
|
||||
lastKnownMessageId: $checkedConvert(
|
||||
'lastKnownMessageId',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
lastCommonReadId: $checkedConvert(
|
||||
'lastCommonReadId',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
timeout: $checkedConvert('timeout', (v) => (v as num?)?.toInt()),
|
||||
setReadMarker: $checkedConvert(
|
||||
'setReadMarker',
|
||||
(v) => $enumDecodeNullable(_$GetChatParamsSwitchEnumMap, v),
|
||||
),
|
||||
includeLastKnown: $checkedConvert(
|
||||
'includeLastKnown',
|
||||
(v) => $enumDecodeNullable(_$GetChatParamsSwitchEnumMap, v),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetChatParamsToJson(
|
||||
GetChatParams instance,
|
||||
|
||||
@@ -2,12 +2,14 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../../../extensions/date_time.dart';
|
||||
import '../../../api_response.dart';
|
||||
import '../../../json_lenient.dart';
|
||||
import '../room/get_room_response.dart';
|
||||
|
||||
part 'get_chat_response.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetChatResponse extends ApiResponse {
|
||||
@JsonKey(fromJson: _messagesFromJson)
|
||||
Set<GetChatResponseObject> data;
|
||||
|
||||
GetChatResponse(this.data);
|
||||
@@ -23,15 +25,20 @@ class GetChatResponse extends ApiResponse {
|
||||
}
|
||||
}
|
||||
|
||||
Set<GetChatResponseObject> _messagesFromJson(Object? raw) =>
|
||||
decodeSetLenient(raw, GetChatResponseObject.fromJson, what: 'message');
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetChatResponseObject {
|
||||
int id;
|
||||
String token;
|
||||
@JsonKey(unknownEnumValue: GetRoomResponseObjectMessageActorType.unknown)
|
||||
GetRoomResponseObjectMessageActorType actorType;
|
||||
String actorId;
|
||||
String actorDisplayName;
|
||||
int timestamp;
|
||||
String systemMessage;
|
||||
@JsonKey(unknownEnumValue: GetRoomResponseObjectMessageType.unknown)
|
||||
GetRoomResponseObjectMessageType messageType;
|
||||
bool isReplyable;
|
||||
String referenceId;
|
||||
@@ -40,6 +47,11 @@ class GetChatResponseObject {
|
||||
List<String>? reactionsSelf;
|
||||
@JsonKey(fromJson: _fromJson)
|
||||
Map<String, RichObjectString>? messageParameters;
|
||||
|
||||
/// The quoted message. When the original is no longer available (expired,
|
||||
/// purged) the server only sends `{id, deleted: true}`; that becomes a
|
||||
/// deleted-comment placeholder so the quote still renders.
|
||||
@JsonKey(fromJson: _parentFromJson)
|
||||
GetChatResponseObject? parent;
|
||||
|
||||
/// Whether the server flagged this message to be rendered as Markdown
|
||||
@@ -70,10 +82,33 @@ class GetChatResponseObject {
|
||||
_$GetChatResponseObjectFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$GetChatResponseObjectToJson(this);
|
||||
|
||||
static GetChatResponseObject getDateDummy(int timestamp) {
|
||||
var elementDate = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
return getTextDummy(elementDate.formatDateRelativeShort());
|
||||
}
|
||||
/// [timestamp] is in seconds.
|
||||
DateTime get sentAt => DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
|
||||
static GetChatResponseObject getDateDummy(int timestamp) => getTextDummy(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
timestamp * 1000,
|
||||
).formatDateRelativeShort(),
|
||||
);
|
||||
|
||||
static GetChatResponseObject deletedPlaceholder(int id) =>
|
||||
GetChatResponseObject(
|
||||
id,
|
||||
'',
|
||||
GetRoomResponseObjectMessageActorType.user,
|
||||
'',
|
||||
'',
|
||||
0,
|
||||
'',
|
||||
GetRoomResponseObjectMessageType.deletedComment,
|
||||
false,
|
||||
'',
|
||||
'Nachricht wurde gelöscht',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
static GetChatResponseObject getTextDummy(String text) =>
|
||||
GetChatResponseObject(
|
||||
@@ -95,14 +130,24 @@ class GetChatResponseObject {
|
||||
);
|
||||
}
|
||||
|
||||
GetChatResponseObject? _parentFromJson(Object? json) {
|
||||
if (json is! Map<String, dynamic>) return null;
|
||||
if (json['deleted'] == true) {
|
||||
return GetChatResponseObject.deletedPlaceholder(
|
||||
(json['id'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
return GetChatResponseObject.fromJson(json);
|
||||
}
|
||||
|
||||
Map<String, RichObjectString>? _fromJson(dynamic json) {
|
||||
if (json is Map<String, dynamic>) {
|
||||
final data = <String, RichObjectString>{};
|
||||
for (final element in json.keys) {
|
||||
data.putIfAbsent(
|
||||
element,
|
||||
() => RichObjectString.fromJson(json[element] as Map<String, dynamic>),
|
||||
);
|
||||
for (final entry in json.entries) {
|
||||
final value = entry.value;
|
||||
if (value is Map<String, dynamic>) {
|
||||
data[entry.key] = RichObjectString.fromJson(value);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@@ -112,6 +157,7 @@ Map<String, RichObjectString>? _fromJson(dynamic json) {
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class RichObjectString {
|
||||
@JsonKey(unknownEnumValue: RichObjectStringObjectType.unknown)
|
||||
RichObjectStringObjectType type;
|
||||
String id;
|
||||
String name;
|
||||
@@ -143,4 +189,14 @@ enum RichObjectStringObjectType {
|
||||
geoLocation,
|
||||
@JsonValue('call')
|
||||
call,
|
||||
@JsonValue('user-group')
|
||||
userGroup,
|
||||
@JsonValue('circle')
|
||||
circle,
|
||||
@JsonValue('email')
|
||||
email,
|
||||
|
||||
/// Anything else (deck cards, contacts, calendar events, ...); the
|
||||
/// placeholder still resolves to the object's name.
|
||||
unknown,
|
||||
}
|
||||
@@ -7,16 +7,18 @@ part of 'get_chat_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
GetChatResponse _$GetChatResponseFromJson(Map<String, dynamic> json) =>
|
||||
GetChatResponse(
|
||||
(json['data'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetChatResponseObject.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toSet(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('GetChatResponse', json, ($checkedConvert) {
|
||||
final val = GetChatResponse(
|
||||
$checkedConvert('data', (v) => _messagesFromJson(v)),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -26,31 +28,49 @@ Map<String, dynamic> _$GetChatResponseToJson(GetChatResponse instance) =>
|
||||
|
||||
GetChatResponseObject _$GetChatResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetChatResponseObject(
|
||||
(json['id'] as num).toInt(),
|
||||
json['token'] as String,
|
||||
$enumDecode(
|
||||
_$GetRoomResponseObjectMessageActorTypeEnumMap,
|
||||
json['actorType'],
|
||||
),
|
||||
json['actorId'] as String,
|
||||
json['actorDisplayName'] as String,
|
||||
(json['timestamp'] as num).toInt(),
|
||||
json['systemMessage'] as String,
|
||||
$enumDecode(_$GetRoomResponseObjectMessageTypeEnumMap, json['messageType']),
|
||||
json['isReplyable'] as bool,
|
||||
json['referenceId'] as String,
|
||||
json['message'] as String,
|
||||
_fromJson(json['messageParameters']),
|
||||
(json['reactions'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||
),
|
||||
(json['reactionsSelf'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
json['parent'] == null
|
||||
? null
|
||||
: GetChatResponseObject.fromJson(json['parent'] as Map<String, dynamic>),
|
||||
json['markdown'] as bool? ?? false,
|
||||
);
|
||||
) => $checkedCreate('GetChatResponseObject', json, ($checkedConvert) {
|
||||
final val = GetChatResponseObject(
|
||||
$checkedConvert('id', (v) => (v as num).toInt()),
|
||||
$checkedConvert('token', (v) => v as String),
|
||||
$checkedConvert(
|
||||
'actorType',
|
||||
(v) => $enumDecode(
|
||||
_$GetRoomResponseObjectMessageActorTypeEnumMap,
|
||||
v,
|
||||
unknownValue: GetRoomResponseObjectMessageActorType.unknown,
|
||||
),
|
||||
),
|
||||
$checkedConvert('actorId', (v) => v as String),
|
||||
$checkedConvert('actorDisplayName', (v) => v as String),
|
||||
$checkedConvert('timestamp', (v) => (v as num).toInt()),
|
||||
$checkedConvert('systemMessage', (v) => v as String),
|
||||
$checkedConvert(
|
||||
'messageType',
|
||||
(v) => $enumDecode(
|
||||
_$GetRoomResponseObjectMessageTypeEnumMap,
|
||||
v,
|
||||
unknownValue: GetRoomResponseObjectMessageType.unknown,
|
||||
),
|
||||
),
|
||||
$checkedConvert('isReplyable', (v) => v as bool),
|
||||
$checkedConvert('referenceId', (v) => v as String),
|
||||
$checkedConvert('message', (v) => v as String),
|
||||
$checkedConvert('messageParameters', (v) => _fromJson(v)),
|
||||
$checkedConvert(
|
||||
'reactions',
|
||||
(v) => (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||
),
|
||||
),
|
||||
$checkedConvert(
|
||||
'reactionsSelf',
|
||||
(v) => (v as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||
),
|
||||
$checkedConvert('parent', (v) => _parentFromJson(v)),
|
||||
$checkedConvert('markdown', (v) => v as bool? ?? false),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetChatResponseObjectToJson(
|
||||
GetChatResponseObject instance,
|
||||
@@ -83,6 +103,12 @@ const _$GetRoomResponseObjectMessageActorTypeEnumMap = {
|
||||
GetRoomResponseObjectMessageActorType.guest: 'guests',
|
||||
GetRoomResponseObjectMessageActorType.bot: 'bots',
|
||||
GetRoomResponseObjectMessageActorType.bridge: 'bridged',
|
||||
GetRoomResponseObjectMessageActorType.group: 'groups',
|
||||
GetRoomResponseObjectMessageActorType.email: 'emails',
|
||||
GetRoomResponseObjectMessageActorType.circle: 'circles',
|
||||
GetRoomResponseObjectMessageActorType.federatedUser: 'federated_users',
|
||||
GetRoomResponseObjectMessageActorType.phone: 'phones',
|
||||
GetRoomResponseObjectMessageActorType.unknown: 'unknown',
|
||||
};
|
||||
|
||||
const _$GetRoomResponseObjectMessageTypeEnumMap = {
|
||||
@@ -91,16 +117,27 @@ const _$GetRoomResponseObjectMessageTypeEnumMap = {
|
||||
GetRoomResponseObjectMessageType.deletedComment: 'comment_deleted',
|
||||
GetRoomResponseObjectMessageType.system: 'system',
|
||||
GetRoomResponseObjectMessageType.command: 'command',
|
||||
GetRoomResponseObjectMessageType.unknown: 'unknown',
|
||||
};
|
||||
|
||||
RichObjectString _$RichObjectStringFromJson(Map<String, dynamic> json) =>
|
||||
RichObjectString(
|
||||
$enumDecode(_$RichObjectStringObjectTypeEnumMap, json['type']),
|
||||
json['id'] as String,
|
||||
json['name'] as String,
|
||||
json['path'] as String?,
|
||||
json['link'] as String?,
|
||||
);
|
||||
$checkedCreate('RichObjectString', json, ($checkedConvert) {
|
||||
final val = RichObjectString(
|
||||
$checkedConvert(
|
||||
'type',
|
||||
(v) => $enumDecode(
|
||||
_$RichObjectStringObjectTypeEnumMap,
|
||||
v,
|
||||
unknownValue: RichObjectStringObjectType.unknown,
|
||||
),
|
||||
),
|
||||
$checkedConvert('id', (v) => v as String),
|
||||
$checkedConvert('name', (v) => v as String),
|
||||
$checkedConvert('path', (v) => v as String?),
|
||||
$checkedConvert('link', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$RichObjectStringToJson(RichObjectString instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -120,4 +157,8 @@ const _$RichObjectStringObjectTypeEnumMap = {
|
||||
RichObjectStringObjectType.talkPoll: 'talk-poll',
|
||||
RichObjectStringObjectType.geoLocation: 'geo-location',
|
||||
RichObjectStringObjectType.call: 'call',
|
||||
RichObjectStringObjectType.userGroup: 'user-group',
|
||||
RichObjectStringObjectType.circle: 'circle',
|
||||
RichObjectStringObjectType.email: 'email',
|
||||
RichObjectStringObjectType.unknown: 'unknown',
|
||||
};
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../errors/server_exception.dart';
|
||||
import '../../../http_errors.dart';
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import 'fetch_chat_page.dart';
|
||||
import 'get_chat_params.dart';
|
||||
import 'get_chat_response.dart';
|
||||
|
||||
/// Long-poll variant of GetChat (`lookIntoFuture=1`). Bypasses [TalkApi]
|
||||
/// because that layer treats non-2xx as errors, and we need 304 to be a
|
||||
/// normal "no new messages" outcome. `setReadMarker=on` lets the server
|
||||
/// move the read cursor whenever the call returns messages.
|
||||
/// Long-poll variant of GetChat (`lookIntoFuture=1`); HTTP 304 is the normal
|
||||
/// "no new messages" outcome (see [fetchChatPage]). `setReadMarker=on` lets
|
||||
/// the server move the read cursor whenever the call returns messages.
|
||||
class LongPollChat {
|
||||
final String chatToken;
|
||||
final int lastKnownMessageId;
|
||||
@@ -22,37 +17,17 @@ class LongPollChat {
|
||||
});
|
||||
|
||||
/// Returns the response, or `null` on HTTP 304 (server timeout, nothing new).
|
||||
Future<GetChatResponse?> run() async {
|
||||
final params = GetChatParams(
|
||||
Future<GetChatResponse?> run() => fetchChatPage(
|
||||
label: 'LongPollChat',
|
||||
chatToken: chatToken,
|
||||
params: GetChatParams(
|
||||
lookIntoFuture: GetChatParamsSwitch.on,
|
||||
timeout: timeoutSeconds,
|
||||
lastKnownMessageId: lastKnownMessageId,
|
||||
includeLastKnown: GetChatParamsSwitch.off,
|
||||
setReadMarker: GetChatParamsSwitch.on,
|
||||
limit: 100,
|
||||
);
|
||||
final uri = NextcloudOcs.uri(
|
||||
'apps/spreed/api/v1/chat/$chatToken',
|
||||
queryParameters: params.toJson(),
|
||||
);
|
||||
final headers = NextcloudOcs.headers();
|
||||
|
||||
final response = (await sendGuarded(
|
||||
'LongPollChat $uri',
|
||||
() => http
|
||||
.get(uri, headers: headers)
|
||||
.timeout(Duration(seconds: timeoutSeconds + 15)),
|
||||
))!;
|
||||
|
||||
final status = response.statusCode;
|
||||
if (status == 304) return null;
|
||||
if (status >= 200 && status < 300) {
|
||||
return GetChatResponse.fromJson(NextcloudOcs.decode(response.body))
|
||||
..headers = response.headers;
|
||||
}
|
||||
throw ServerException(
|
||||
statusCode: status,
|
||||
technicalDetails: 'LongPollChat $uri: HTTP $status',
|
||||
);
|
||||
}
|
||||
),
|
||||
timeout: Duration(seconds: timeoutSeconds + 15),
|
||||
);
|
||||
}
|
||||
@@ -7,14 +7,18 @@ part of 'create_poll_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
CreatePollParams _$CreatePollParamsFromJson(Map<String, dynamic> json) =>
|
||||
CreatePollParams(
|
||||
question: json['question'] as String,
|
||||
options: (json['options'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
resultMode: (json['resultMode'] as num).toInt(),
|
||||
maxVotes: (json['maxVotes'] as num).toInt(),
|
||||
);
|
||||
$checkedCreate('CreatePollParams', json, ($checkedConvert) {
|
||||
final val = CreatePollParams(
|
||||
question: $checkedConvert('question', (v) => v as String),
|
||||
options: $checkedConvert(
|
||||
'options',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
resultMode: $checkedConvert('resultMode', (v) => (v as num).toInt()),
|
||||
maxVotes: $checkedConvert('maxVotes', (v) => (v as num).toInt()),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CreatePollParamsToJson(CreatePollParams instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -7,14 +7,17 @@ part of 'create_room_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
CreateRoomParams _$CreateRoomParamsFromJson(Map<String, dynamic> json) =>
|
||||
CreateRoomParams(
|
||||
roomType: (json['roomType'] as num).toInt(),
|
||||
invite: json['invite'] as String,
|
||||
source: json['source'] as String?,
|
||||
roomName: json['roomName'] as String?,
|
||||
objectType: json['objectType'] as String?,
|
||||
objectId: json['objectId'] as String?,
|
||||
);
|
||||
$checkedCreate('CreateRoomParams', json, ($checkedConvert) {
|
||||
final val = CreateRoomParams(
|
||||
roomType: $checkedConvert('roomType', (v) => (v as num).toInt()),
|
||||
invite: $checkedConvert('invite', (v) => v as String),
|
||||
source: $checkedConvert('source', (v) => v as String?),
|
||||
roomName: $checkedConvert('roomName', (v) => v as String?),
|
||||
objectType: $checkedConvert('objectType', (v) => v as String?),
|
||||
objectId: $checkedConvert('objectId', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CreateRoomParamsToJson(CreateRoomParams instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -7,12 +7,21 @@ part of 'create_room_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
CreateRoomResponse _$CreateRoomResponseFromJson(Map<String, dynamic> json) =>
|
||||
CreateRoomResponse(
|
||||
CreateRoomResponseData.fromJson(json['data'] as Map<String, dynamic>),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('CreateRoomResponse', json, ($checkedConvert) {
|
||||
final val = CreateRoomResponse(
|
||||
$checkedConvert(
|
||||
'data',
|
||||
(v) => CreateRoomResponseData.fromJson(v as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CreateRoomResponseToJson(CreateRoomResponse instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -22,7 +31,12 @@ Map<String, dynamic> _$CreateRoomResponseToJson(CreateRoomResponse instance) =>
|
||||
|
||||
CreateRoomResponseData _$CreateRoomResponseDataFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => CreateRoomResponseData(json['token'] as String);
|
||||
) => $checkedCreate('CreateRoomResponseData', json, ($checkedConvert) {
|
||||
final val = CreateRoomResponseData(
|
||||
$checkedConvert('token', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CreateRoomResponseDataToJson(
|
||||
CreateRoomResponseData instance,
|
||||
|
||||
@@ -8,7 +8,12 @@ part of 'delete_react_message_params.dart';
|
||||
|
||||
DeleteReactMessageParams _$DeleteReactMessageParamsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => DeleteReactMessageParams(json['reaction'] as String);
|
||||
) => $checkedCreate('DeleteReactMessageParams', json, ($checkedConvert) {
|
||||
final val = DeleteReactMessageParams(
|
||||
$checkedConvert('reaction', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$DeleteReactMessageParamsToJson(
|
||||
DeleteReactMessageParams instance,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_participants_response.dart';
|
||||
@@ -11,11 +9,4 @@ class GetParticipants extends TalkApi<GetParticipantsResponse> {
|
||||
@override
|
||||
GetParticipantsResponse assemble(String raw) =>
|
||||
GetParticipantsResponse.fromJson(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../../api_response.dart';
|
||||
import '../../../json_lenient.dart';
|
||||
|
||||
part 'get_participants_response.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetParticipantsResponse extends ApiResponse {
|
||||
@JsonKey(fromJson: _participantsFromJson)
|
||||
Set<GetParticipantsResponseObject> data;
|
||||
|
||||
GetParticipantsResponse(this.data);
|
||||
@@ -15,15 +17,25 @@ class GetParticipantsResponse extends ApiResponse {
|
||||
Map<String, dynamic> toJson() => _$GetParticipantsResponseToJson(this);
|
||||
}
|
||||
|
||||
Set<GetParticipantsResponseObject> _participantsFromJson(Object? raw) =>
|
||||
decodeSetLenient(
|
||||
raw,
|
||||
GetParticipantsResponseObject.fromJson,
|
||||
what: 'participant',
|
||||
);
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetParticipantsResponseObject {
|
||||
int attendeeId;
|
||||
String actorType;
|
||||
String actorId;
|
||||
String displayName;
|
||||
@JsonKey(unknownEnumValue: GetParticipantsResponseObjectParticipantType.user)
|
||||
GetParticipantsResponseObjectParticipantType participantType;
|
||||
int lastPing;
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags inCall;
|
||||
|
||||
/// Bitmask: 1 = in call, 2 = with audio, 4 = with video, 8 = via phone.
|
||||
int inCall;
|
||||
int permissions;
|
||||
int attendeePermissions;
|
||||
String? sessionId;
|
||||
@@ -54,6 +66,11 @@ class GetParticipantsResponseObject {
|
||||
factory GetParticipantsResponseObject.fromJson(Map<String, dynamic> json) =>
|
||||
_$GetParticipantsResponseObjectFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$GetParticipantsResponseObjectToJson(this);
|
||||
|
||||
bool get isInCall => inCall & 1 != 0;
|
||||
bool get inCallWithAudio => inCall & 2 != 0;
|
||||
bool get inCallWithVideo => inCall & 4 != 0;
|
||||
bool get inCallViaPhone => inCall & 8 != 0;
|
||||
}
|
||||
|
||||
enum GetParticipantsResponseObjectParticipantType {
|
||||
@@ -73,16 +90,3 @@ enum GetParticipantsResponseObjectParticipantType {
|
||||
const GetParticipantsResponseObjectParticipantType(this.prettyName);
|
||||
final String prettyName;
|
||||
}
|
||||
|
||||
enum GetParticipantsResponseObjectParticipantsInCallFlags {
|
||||
@JsonValue(0)
|
||||
disconnected,
|
||||
@JsonValue(1)
|
||||
inCall,
|
||||
@JsonValue(2)
|
||||
providesAudio,
|
||||
@JsonValue(3)
|
||||
providesVideo,
|
||||
@JsonValue(4)
|
||||
usesSipDialIn,
|
||||
}
|
||||
@@ -8,19 +8,18 @@ part of 'get_participants_response.dart';
|
||||
|
||||
GetParticipantsResponse _$GetParticipantsResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
GetParticipantsResponse(
|
||||
(json['data'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetParticipantsResponseObject.fromJson(
|
||||
e as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toSet(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('GetParticipantsResponse', json, ($checkedConvert) {
|
||||
final val = GetParticipantsResponse(
|
||||
$checkedConvert('data', (v) => _participantsFromJson(v)),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetParticipantsResponseToJson(
|
||||
GetParticipantsResponse instance,
|
||||
@@ -31,29 +30,36 @@ Map<String, dynamic> _$GetParticipantsResponseToJson(
|
||||
|
||||
GetParticipantsResponseObject _$GetParticipantsResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetParticipantsResponseObject(
|
||||
(json['attendeeId'] as num).toInt(),
|
||||
json['actorType'] as String,
|
||||
json['actorId'] as String,
|
||||
json['displayName'] as String,
|
||||
$enumDecode(
|
||||
_$GetParticipantsResponseObjectParticipantTypeEnumMap,
|
||||
json['participantType'],
|
||||
),
|
||||
(json['lastPing'] as num).toInt(),
|
||||
$enumDecode(
|
||||
_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap,
|
||||
json['inCall'],
|
||||
),
|
||||
(json['permissions'] as num).toInt(),
|
||||
(json['attendeePermissions'] as num).toInt(),
|
||||
json['sessionId'] as String?,
|
||||
(json['sessionIds'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
json['status'] as String?,
|
||||
json['statusIcon'] as String?,
|
||||
json['statusMessage'] as String?,
|
||||
json['roomToken'] as String?,
|
||||
);
|
||||
) => $checkedCreate('GetParticipantsResponseObject', json, ($checkedConvert) {
|
||||
final val = GetParticipantsResponseObject(
|
||||
$checkedConvert('attendeeId', (v) => (v as num).toInt()),
|
||||
$checkedConvert('actorType', (v) => v as String),
|
||||
$checkedConvert('actorId', (v) => v as String),
|
||||
$checkedConvert('displayName', (v) => v as String),
|
||||
$checkedConvert(
|
||||
'participantType',
|
||||
(v) => $enumDecode(
|
||||
_$GetParticipantsResponseObjectParticipantTypeEnumMap,
|
||||
v,
|
||||
unknownValue: GetParticipantsResponseObjectParticipantType.user,
|
||||
),
|
||||
),
|
||||
$checkedConvert('lastPing', (v) => (v as num).toInt()),
|
||||
$checkedConvert('inCall', (v) => (v as num).toInt()),
|
||||
$checkedConvert('permissions', (v) => (v as num).toInt()),
|
||||
$checkedConvert('attendeePermissions', (v) => (v as num).toInt()),
|
||||
$checkedConvert('sessionId', (v) => v as String?),
|
||||
$checkedConvert(
|
||||
'sessionIds',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
$checkedConvert('status', (v) => v as String?),
|
||||
$checkedConvert('statusIcon', (v) => v as String?),
|
||||
$checkedConvert('statusMessage', (v) => v as String?),
|
||||
$checkedConvert('roomToken', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetParticipantsResponseObjectToJson(
|
||||
GetParticipantsResponseObject instance,
|
||||
@@ -66,9 +72,7 @@ Map<String, dynamic> _$GetParticipantsResponseObjectToJson(
|
||||
_$GetParticipantsResponseObjectParticipantTypeEnumMap[instance
|
||||
.participantType]!,
|
||||
'lastPing': instance.lastPing,
|
||||
'inCall':
|
||||
_$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap[instance
|
||||
.inCall]!,
|
||||
'inCall': instance.inCall,
|
||||
'permissions': instance.permissions,
|
||||
'attendeePermissions': instance.attendeePermissions,
|
||||
'sessionId': instance.sessionId,
|
||||
@@ -87,11 +91,3 @@ const _$GetParticipantsResponseObjectParticipantTypeEnumMap = {
|
||||
GetParticipantsResponseObjectParticipantType.userFollowingPublicLink: 5,
|
||||
GetParticipantsResponseObjectParticipantType.guestWithModeratorPermissions: 6,
|
||||
};
|
||||
|
||||
const _$GetParticipantsResponseObjectParticipantsInCallFlagsEnumMap = {
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags.disconnected: 0,
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags.inCall: 1,
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags.providesAudio: 2,
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags.providesVideo: 3,
|
||||
GetParticipantsResponseObjectParticipantsInCallFlags.usesSipDialIn: 4,
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_poll_state_response.dart';
|
||||
@@ -13,11 +11,4 @@ class GetPollState extends TalkApi<GetPollStateResponse> {
|
||||
@override
|
||||
GetPollStateResponse assemble(String raw) =>
|
||||
GetPollStateResponse.fromJson(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -8,15 +8,21 @@ part of 'get_poll_state_response.dart';
|
||||
|
||||
GetPollStateResponse _$GetPollStateResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
GetPollStateResponse(
|
||||
GetPollStateResponseObject.fromJson(
|
||||
json['data'] as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('GetPollStateResponse', json, ($checkedConvert) {
|
||||
final val = GetPollStateResponse(
|
||||
$checkedConvert(
|
||||
'data',
|
||||
(v) => GetPollStateResponseObject.fromJson(v as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetPollStateResponseToJson(
|
||||
GetPollStateResponse instance,
|
||||
@@ -27,21 +33,30 @@ Map<String, dynamic> _$GetPollStateResponseToJson(
|
||||
|
||||
GetPollStateResponseObject _$GetPollStateResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetPollStateResponseObject(
|
||||
(json['id'] as num).toInt(),
|
||||
json['question'] as String,
|
||||
(json['options'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
json['votes'],
|
||||
json['actorType'] as String,
|
||||
json['actorId'] as String,
|
||||
json['actorDisplayName'] as String,
|
||||
(json['status'] as num).toInt(),
|
||||
(json['resultMode'] as num).toInt(),
|
||||
(json['maxVotes'] as num).toInt(),
|
||||
(json['votedSelf'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
(json['numVoters'] as num?)?.toInt(),
|
||||
json['details'] as List<dynamic>?,
|
||||
);
|
||||
) => $checkedCreate('GetPollStateResponseObject', json, ($checkedConvert) {
|
||||
final val = GetPollStateResponseObject(
|
||||
$checkedConvert('id', (v) => (v as num).toInt()),
|
||||
$checkedConvert('question', (v) => v as String),
|
||||
$checkedConvert(
|
||||
'options',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
$checkedConvert('votes', (v) => v),
|
||||
$checkedConvert('actorType', (v) => v as String),
|
||||
$checkedConvert('actorId', (v) => v as String),
|
||||
$checkedConvert('actorDisplayName', (v) => v as String),
|
||||
$checkedConvert('status', (v) => (v as num).toInt()),
|
||||
$checkedConvert('resultMode', (v) => (v as num).toInt()),
|
||||
$checkedConvert('maxVotes', (v) => (v as num).toInt()),
|
||||
$checkedConvert(
|
||||
'votedSelf',
|
||||
(v) => (v as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
),
|
||||
$checkedConvert('numVoters', (v) => (v as num?)?.toInt()),
|
||||
$checkedConvert('details', (v) => v as List<dynamic>?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetPollStateResponseObjectToJson(
|
||||
GetPollStateResponseObject instance,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import '../../../api_params.dart';
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_reactions_response.dart';
|
||||
@@ -15,11 +11,4 @@ class GetReactions extends TalkApi<GetReactionsResponse> {
|
||||
@override
|
||||
GetReactionsResponse assemble(String raw) =>
|
||||
GetReactionsResponse.fromJson(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<Response>? request(
|
||||
Uri uri,
|
||||
ApiParams? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ class GetReactionsResponse extends ApiResponse {
|
||||
|
||||
@JsonSerializable()
|
||||
class GetReactionsResponseObject {
|
||||
@JsonKey(unknownEnumValue: GetReactionsResponseObjectActorType.unknown)
|
||||
GetReactionsResponseObjectActorType actorType;
|
||||
String actorId;
|
||||
String actorDisplayName;
|
||||
@@ -39,4 +40,11 @@ enum GetReactionsResponseObjectActorType {
|
||||
guests,
|
||||
@JsonValue('users')
|
||||
users,
|
||||
@JsonValue('emails')
|
||||
emails,
|
||||
@JsonValue('bots')
|
||||
bots,
|
||||
@JsonValue('federated_users')
|
||||
federatedUsers,
|
||||
unknown,
|
||||
}
|
||||
@@ -8,24 +8,32 @@ part of 'get_reactions_response.dart';
|
||||
|
||||
GetReactionsResponse _$GetReactionsResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
GetReactionsResponse(
|
||||
(json['data'] as Map<String, dynamic>).map(
|
||||
(k, e) => MapEntry(
|
||||
k,
|
||||
(e as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetReactionsResponseObject.fromJson(
|
||||
e as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
) => $checkedCreate('GetReactionsResponse', json, ($checkedConvert) {
|
||||
final val = GetReactionsResponse(
|
||||
$checkedConvert(
|
||||
'data',
|
||||
(v) => (v as Map<String, dynamic>).map(
|
||||
(k, e) => MapEntry(
|
||||
k,
|
||||
(e as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetReactionsResponseObject.fromJson(
|
||||
e as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetReactionsResponseToJson(
|
||||
GetReactionsResponse instance,
|
||||
@@ -38,12 +46,22 @@ Map<String, dynamic> _$GetReactionsResponseToJson(
|
||||
|
||||
GetReactionsResponseObject _$GetReactionsResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetReactionsResponseObject(
|
||||
$enumDecode(_$GetReactionsResponseObjectActorTypeEnumMap, json['actorType']),
|
||||
json['actorId'] as String,
|
||||
json['actorDisplayName'] as String,
|
||||
(json['timestamp'] as num).toInt(),
|
||||
);
|
||||
) => $checkedCreate('GetReactionsResponseObject', json, ($checkedConvert) {
|
||||
final val = GetReactionsResponseObject(
|
||||
$checkedConvert(
|
||||
'actorType',
|
||||
(v) => $enumDecode(
|
||||
_$GetReactionsResponseObjectActorTypeEnumMap,
|
||||
v,
|
||||
unknownValue: GetReactionsResponseObjectActorType.unknown,
|
||||
),
|
||||
),
|
||||
$checkedConvert('actorId', (v) => v as String),
|
||||
$checkedConvert('actorDisplayName', (v) => v as String),
|
||||
$checkedConvert('timestamp', (v) => (v as num).toInt()),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetReactionsResponseObjectToJson(
|
||||
GetReactionsResponseObject instance,
|
||||
@@ -58,4 +76,8 @@ Map<String, dynamic> _$GetReactionsResponseObjectToJson(
|
||||
const _$GetReactionsResponseObjectActorTypeEnumMap = {
|
||||
GetReactionsResponseObjectActorType.guests: 'guests',
|
||||
GetReactionsResponseObjectActorType.users: 'users',
|
||||
GetReactionsResponseObjectActorType.emails: 'emails',
|
||||
GetReactionsResponseObjectActorType.bots: 'bots',
|
||||
GetReactionsResponseObjectActorType.federatedUsers: 'federated_users',
|
||||
GetReactionsResponseObjectActorType.unknown: 'unknown',
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_shared_items_response.dart';
|
||||
@@ -30,11 +28,4 @@ class GetSharedItems extends TalkApi<GetSharedItemsResponse> {
|
||||
@override
|
||||
GetSharedItemsResponse assemble(String raw) =>
|
||||
GetSharedItemsResponse.fromOcs(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_shared_items_overview_response.dart';
|
||||
@@ -18,11 +16,4 @@ class GetSharedItemsOverview extends TalkApi<GetSharedItemsOverviewResponse> {
|
||||
@override
|
||||
GetSharedItemsOverviewResponse assemble(String raw) =>
|
||||
GetSharedItemsOverviewResponse.fromOcs(NextcloudOcs.decode(raw));
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
@@ -7,7 +7,12 @@ part of 'react_message_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
ReactMessageParams _$ReactMessageParamsFromJson(Map<String, dynamic> json) =>
|
||||
ReactMessageParams(json['reaction'] as String);
|
||||
$checkedCreate('ReactMessageParams', json, ($checkedConvert) {
|
||||
final val = ReactMessageParams(
|
||||
$checkedConvert('reaction', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$ReactMessageParamsToJson(ReactMessageParams instance) =>
|
||||
<String, dynamic>{'reaction': instance.reaction};
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../../json_lenient.dart';
|
||||
import '../../nextcloud_ocs.dart';
|
||||
import '../talk_api.dart';
|
||||
import 'get_room_params.dart';
|
||||
@@ -17,14 +17,8 @@ class GetRoom extends TalkApi<GetRoomResponse> {
|
||||
@override
|
||||
Future<GetRoomResponse> assembleAsync(String raw) =>
|
||||
compute(_parseRooms, raw);
|
||||
|
||||
@override
|
||||
Future<http.Response> request(
|
||||
Uri uri,
|
||||
Object? body,
|
||||
Map<String, String>? headers,
|
||||
) => http.get(uri, headers: headers);
|
||||
}
|
||||
|
||||
GetRoomResponse _parseRooms(String raw) =>
|
||||
GetRoomResponse.fromJson(NextcloudOcs.decode(raw));
|
||||
GetRoomResponse.fromJson(NextcloudOcs.decode(raw))
|
||||
..parseWarnings = drainDecodeWarnings();
|
||||
@@ -4,7 +4,7 @@ import '../../../api_params.dart';
|
||||
|
||||
part 'get_room_params.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
@JsonSerializable(explicitToJson: true, includeIfNull: false)
|
||||
class GetRoomParams extends ApiParams {
|
||||
GetRoomParamsStatusUpdate? noStatusUpdate;
|
||||
@JsonKey(toJson: _format)
|
||||
|
||||
@@ -7,22 +7,28 @@ part of 'get_room_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
GetRoomParams _$GetRoomParamsFromJson(Map<String, dynamic> json) =>
|
||||
GetRoomParams(
|
||||
noStatusUpdate: $enumDecodeNullable(
|
||||
_$GetRoomParamsStatusUpdateEnumMap,
|
||||
json['noStatusUpdate'],
|
||||
),
|
||||
includeStatus: json['includeStatus'] as bool?,
|
||||
modifiedSince: (json['modifiedSince'] as num?)?.toInt(),
|
||||
);
|
||||
$checkedCreate('GetRoomParams', json, ($checkedConvert) {
|
||||
final val = GetRoomParams(
|
||||
noStatusUpdate: $checkedConvert(
|
||||
'noStatusUpdate',
|
||||
(v) => $enumDecodeNullable(_$GetRoomParamsStatusUpdateEnumMap, v),
|
||||
),
|
||||
includeStatus: $checkedConvert('includeStatus', (v) => v as bool?),
|
||||
modifiedSince: $checkedConvert(
|
||||
'modifiedSince',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetRoomParamsToJson(
|
||||
GetRoomParams instance,
|
||||
) => <String, dynamic>{
|
||||
'noStatusUpdate': _$GetRoomParamsStatusUpdateEnumMap[instance.noStatusUpdate],
|
||||
'includeStatus': GetRoomParams._format(instance.includeStatus),
|
||||
'modifiedSince': instance.modifiedSince,
|
||||
};
|
||||
Map<String, dynamic> _$GetRoomParamsToJson(GetRoomParams instance) =>
|
||||
<String, dynamic>{
|
||||
'noStatusUpdate':
|
||||
?_$GetRoomParamsStatusUpdateEnumMap[instance.noStatusUpdate],
|
||||
'includeStatus': GetRoomParams._format(instance.includeStatus),
|
||||
'modifiedSince': ?instance.modifiedSince,
|
||||
};
|
||||
|
||||
const _$GetRoomParamsStatusUpdateEnumMap = {
|
||||
GetRoomParamsStatusUpdate.defaults: 0,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../../../api_response.dart';
|
||||
import '../../../json_lenient.dart';
|
||||
import '../chat/get_chat_response.dart';
|
||||
|
||||
part 'get_room_response.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetRoomResponse extends ApiResponse {
|
||||
@JsonKey(fromJson: _roomsFromJson)
|
||||
Set<GetRoomResponseObject> data;
|
||||
|
||||
GetRoomResponse(this.data);
|
||||
@@ -33,10 +35,14 @@ class GetRoomResponse extends ApiResponse {
|
||||
}
|
||||
}
|
||||
|
||||
Set<GetRoomResponseObject> _roomsFromJson(Object? raw) =>
|
||||
decodeSetLenient(raw, GetRoomResponseObject.fromJson, what: 'room');
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class GetRoomResponseObject {
|
||||
int id;
|
||||
String token;
|
||||
@JsonKey(unknownEnumValue: GetRoomResponseObjectConversationType.unknown)
|
||||
GetRoomResponseObjectConversationType type;
|
||||
String name;
|
||||
String displayName;
|
||||
@@ -55,13 +61,20 @@ class GetRoomResponseObject {
|
||||
bool canLeaveConversation;
|
||||
int lastActivity;
|
||||
bool isFavorite;
|
||||
@JsonKey(
|
||||
unknownEnumValue:
|
||||
GetRoomResponseObjectParticipantNotificationLevel.defaultLevel,
|
||||
)
|
||||
GetRoomResponseObjectParticipantNotificationLevel notificationLevel;
|
||||
int unreadMessages;
|
||||
bool unreadMention;
|
||||
bool unreadMentionDirect;
|
||||
int lastReadMessage;
|
||||
int lastCommonReadMessage;
|
||||
GetChatResponseObject lastMessage;
|
||||
|
||||
/// Omitted by the server for rooms without a visible message (empty room,
|
||||
/// last message expired or hidden from this user, federated conversation).
|
||||
GetChatResponseObject? lastMessage;
|
||||
String? status;
|
||||
String? statusIcon;
|
||||
String? statusMessage;
|
||||
@@ -117,6 +130,9 @@ enum GetRoomResponseObjectConversationType {
|
||||
deleted,
|
||||
@JsonValue(6)
|
||||
noteToSelf,
|
||||
|
||||
/// Fallback for types this app does not know yet; treated like a group.
|
||||
unknown,
|
||||
}
|
||||
|
||||
enum GetRoomResponseObjectParticipantNotificationLevel {
|
||||
@@ -147,6 +163,17 @@ enum GetRoomResponseObjectMessageActorType {
|
||||
bot,
|
||||
@JsonValue('bridged')
|
||||
bridge,
|
||||
@JsonValue('groups')
|
||||
group,
|
||||
@JsonValue('emails')
|
||||
email,
|
||||
@JsonValue('circles')
|
||||
circle,
|
||||
@JsonValue('federated_users')
|
||||
federatedUser,
|
||||
@JsonValue('phones')
|
||||
phone,
|
||||
unknown,
|
||||
}
|
||||
|
||||
enum GetRoomResponseObjectMessageType {
|
||||
@@ -160,4 +187,8 @@ enum GetRoomResponseObjectMessageType {
|
||||
system,
|
||||
@JsonValue('command')
|
||||
command,
|
||||
|
||||
/// Fallback for types this app does not know yet (e.g. call recordings);
|
||||
/// rendered like a plain comment.
|
||||
unknown,
|
||||
}
|
||||
@@ -7,16 +7,18 @@ part of 'get_room_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
GetRoomResponse _$GetRoomResponseFromJson(Map<String, dynamic> json) =>
|
||||
GetRoomResponse(
|
||||
(json['data'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => GetRoomResponseObject.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toSet(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('GetRoomResponse', json, ($checkedConvert) {
|
||||
final val = GetRoomResponse(
|
||||
$checkedConvert('data', (v) => _roomsFromJson(v)),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -26,41 +28,61 @@ Map<String, dynamic> _$GetRoomResponseToJson(GetRoomResponse instance) =>
|
||||
|
||||
GetRoomResponseObject _$GetRoomResponseObjectFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetRoomResponseObject(
|
||||
(json['id'] as num).toInt(),
|
||||
json['token'] as String,
|
||||
$enumDecode(_$GetRoomResponseObjectConversationTypeEnumMap, json['type']),
|
||||
json['name'] as String,
|
||||
json['displayName'] as String,
|
||||
json['description'] as String,
|
||||
(json['participantType'] as num).toInt(),
|
||||
(json['participantFlags'] as num).toInt(),
|
||||
(json['readOnly'] as num).toInt(),
|
||||
(json['listable'] as num).toInt(),
|
||||
(json['lastPing'] as num).toInt(),
|
||||
json['sessionId'] as String,
|
||||
json['hasPassword'] as bool,
|
||||
json['hasCall'] as bool,
|
||||
(json['callFlag'] as num).toInt(),
|
||||
json['canStartCall'] as bool,
|
||||
json['canDeleteConversation'] as bool,
|
||||
json['canLeaveConversation'] as bool,
|
||||
(json['lastActivity'] as num).toInt(),
|
||||
json['isFavorite'] as bool,
|
||||
$enumDecode(
|
||||
_$GetRoomResponseObjectParticipantNotificationLevelEnumMap,
|
||||
json['notificationLevel'],
|
||||
),
|
||||
(json['unreadMessages'] as num).toInt(),
|
||||
json['unreadMention'] as bool,
|
||||
json['unreadMentionDirect'] as bool,
|
||||
(json['lastReadMessage'] as num).toInt(),
|
||||
(json['lastCommonReadMessage'] as num).toInt(),
|
||||
GetChatResponseObject.fromJson(json['lastMessage'] as Map<String, dynamic>),
|
||||
json['status'] as String?,
|
||||
json['statusIcon'] as String?,
|
||||
json['statusMessage'] as String?,
|
||||
);
|
||||
) => $checkedCreate('GetRoomResponseObject', json, ($checkedConvert) {
|
||||
final val = GetRoomResponseObject(
|
||||
$checkedConvert('id', (v) => (v as num).toInt()),
|
||||
$checkedConvert('token', (v) => v as String),
|
||||
$checkedConvert(
|
||||
'type',
|
||||
(v) => $enumDecode(
|
||||
_$GetRoomResponseObjectConversationTypeEnumMap,
|
||||
v,
|
||||
unknownValue: GetRoomResponseObjectConversationType.unknown,
|
||||
),
|
||||
),
|
||||
$checkedConvert('name', (v) => v as String),
|
||||
$checkedConvert('displayName', (v) => v as String),
|
||||
$checkedConvert('description', (v) => v as String),
|
||||
$checkedConvert('participantType', (v) => (v as num).toInt()),
|
||||
$checkedConvert('participantFlags', (v) => (v as num).toInt()),
|
||||
$checkedConvert('readOnly', (v) => (v as num).toInt()),
|
||||
$checkedConvert('listable', (v) => (v as num).toInt()),
|
||||
$checkedConvert('lastPing', (v) => (v as num).toInt()),
|
||||
$checkedConvert('sessionId', (v) => v as String),
|
||||
$checkedConvert('hasPassword', (v) => v as bool),
|
||||
$checkedConvert('hasCall', (v) => v as bool),
|
||||
$checkedConvert('callFlag', (v) => (v as num).toInt()),
|
||||
$checkedConvert('canStartCall', (v) => v as bool),
|
||||
$checkedConvert('canDeleteConversation', (v) => v as bool),
|
||||
$checkedConvert('canLeaveConversation', (v) => v as bool),
|
||||
$checkedConvert('lastActivity', (v) => (v as num).toInt()),
|
||||
$checkedConvert('isFavorite', (v) => v as bool),
|
||||
$checkedConvert(
|
||||
'notificationLevel',
|
||||
(v) => $enumDecode(
|
||||
_$GetRoomResponseObjectParticipantNotificationLevelEnumMap,
|
||||
v,
|
||||
unknownValue:
|
||||
GetRoomResponseObjectParticipantNotificationLevel.defaultLevel,
|
||||
),
|
||||
),
|
||||
$checkedConvert('unreadMessages', (v) => (v as num).toInt()),
|
||||
$checkedConvert('unreadMention', (v) => v as bool),
|
||||
$checkedConvert('unreadMentionDirect', (v) => v as bool),
|
||||
$checkedConvert('lastReadMessage', (v) => (v as num).toInt()),
|
||||
$checkedConvert('lastCommonReadMessage', (v) => (v as num).toInt()),
|
||||
$checkedConvert(
|
||||
'lastMessage',
|
||||
(v) => v == null
|
||||
? null
|
||||
: GetChatResponseObject.fromJson(v as Map<String, dynamic>),
|
||||
),
|
||||
$checkedConvert('status', (v) => v as String?),
|
||||
$checkedConvert('statusIcon', (v) => v as String?),
|
||||
$checkedConvert('statusMessage', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetRoomResponseObjectToJson(
|
||||
GetRoomResponseObject instance,
|
||||
@@ -93,7 +115,7 @@ Map<String, dynamic> _$GetRoomResponseObjectToJson(
|
||||
'unreadMentionDirect': instance.unreadMentionDirect,
|
||||
'lastReadMessage': instance.lastReadMessage,
|
||||
'lastCommonReadMessage': instance.lastCommonReadMessage,
|
||||
'lastMessage': instance.lastMessage.toJson(),
|
||||
'lastMessage': instance.lastMessage?.toJson(),
|
||||
'status': instance.status,
|
||||
'statusIcon': instance.statusIcon,
|
||||
'statusMessage': instance.statusMessage,
|
||||
@@ -106,6 +128,7 @@ const _$GetRoomResponseObjectConversationTypeEnumMap = {
|
||||
GetRoomResponseObjectConversationType.changelog: 4,
|
||||
GetRoomResponseObjectConversationType.deleted: 5,
|
||||
GetRoomResponseObjectConversationType.noteToSelf: 6,
|
||||
GetRoomResponseObjectConversationType.unknown: 'unknown',
|
||||
};
|
||||
|
||||
const _$GetRoomResponseObjectParticipantNotificationLevelEnumMap = {
|
||||
|
||||
@@ -7,10 +7,13 @@ part of 'send_message_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
SendMessageParams _$SendMessageParamsFromJson(Map<String, dynamic> json) =>
|
||||
SendMessageParams(
|
||||
json['message'] as String,
|
||||
replyTo: json['replyTo'] as String?,
|
||||
);
|
||||
$checkedCreate('SendMessageParams', json, ($checkedConvert) {
|
||||
final val = SendMessageParams(
|
||||
$checkedConvert('message', (v) => v as String),
|
||||
replyTo: $checkedConvert('replyTo', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$SendMessageParamsToJson(SendMessageParams instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -7,9 +7,15 @@ part of 'set_read_marker_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
SetReadMarkerParams _$SetReadMarkerParamsFromJson(Map<String, dynamic> json) =>
|
||||
SetReadMarkerParams(
|
||||
lastReadMessage: (json['lastReadMessage'] as num?)?.toInt(),
|
||||
);
|
||||
$checkedCreate('SetReadMarkerParams', json, ($checkedConvert) {
|
||||
final val = SetReadMarkerParams(
|
||||
lastReadMessage: $checkedConvert(
|
||||
'lastReadMessage',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$SetReadMarkerParamsToJson(
|
||||
SetReadMarkerParams instance,
|
||||
|
||||
@@ -8,6 +8,8 @@ import '../../errors/network_exception.dart';
|
||||
import '../../errors/parse_exception.dart';
|
||||
import '../../errors/talk_exception.dart';
|
||||
import '../../http_errors.dart';
|
||||
import '../../json_lenient.dart';
|
||||
import '../../marianumconnect/queries/report_client_error/client_error_reporter.dart';
|
||||
import '../nextcloud_ocs.dart';
|
||||
import 'talk_error.dart';
|
||||
|
||||
@@ -23,11 +25,12 @@ abstract class TalkApi<T extends ApiResponse?> {
|
||||
|
||||
TalkApi(this.path, this.body, {this.headers, this.getParameters});
|
||||
|
||||
/// Defaults to a plain GET; endpoints with a body or another verb override it.
|
||||
Future<http.Response>? request(
|
||||
Uri uri,
|
||||
ApiParams? body,
|
||||
Map<String, String>? headers,
|
||||
);
|
||||
) => http.get(uri, headers: headers);
|
||||
T assemble(String raw);
|
||||
|
||||
/// Override to parse large payloads off the UI isolate (e.g. via `compute`
|
||||
@@ -72,12 +75,17 @@ abstract class TalkApi<T extends ApiResponse?> {
|
||||
throwForStatus(status, detail);
|
||||
}
|
||||
|
||||
final T assembled;
|
||||
try {
|
||||
final assembled = await assembleAsync(data.body);
|
||||
assembled?.headers = data.headers;
|
||||
return assembled;
|
||||
assembled = await assembleAsync(data.body);
|
||||
} catch (e) {
|
||||
throw ParseException(technicalDetails: 'Talk $endpoint assemble: $e');
|
||||
}
|
||||
assembled?.headers = data.headers;
|
||||
ClientErrorReporter.reportDecodeWarnings('Talk $path', [
|
||||
...?assembled?.parseWarnings,
|
||||
...drainDecodeWarnings(),
|
||||
]);
|
||||
return assembled;
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,15 @@ part of 'vote_poll_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
VotePollParams _$VotePollParamsFromJson(Map<String, dynamic> json) =>
|
||||
VotePollParams(
|
||||
optionIds: (json['optionIds'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
$checkedCreate('VotePollParams', json, ($checkedConvert) {
|
||||
final val = VotePollParams(
|
||||
optionIds: $checkedConvert(
|
||||
'optionIds',
|
||||
(v) => (v as List<dynamic>).map((e) => (e as num).toInt()).toList(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$VotePollParamsToJson(VotePollParams instance) =>
|
||||
<String, dynamic>{'optionIds': instance.optionIds};
|
||||
@@ -7,23 +7,28 @@ part of 'cacheable_file.dart';
|
||||
// **************************************************************************
|
||||
|
||||
CacheableFile _$CacheableFileFromJson(Map<String, dynamic> json) =>
|
||||
CacheableFile(
|
||||
path: json['path'] as String,
|
||||
isDirectory: json['isDirectory'] as bool,
|
||||
name: json['name'] as String,
|
||||
mimeType: json['mimeType'] as String?,
|
||||
size: (json['size'] as num?)?.toInt(),
|
||||
eTag: json['eTag'] as String?,
|
||||
createdAt: json['createdAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['createdAt'] as String),
|
||||
modifiedAt: json['modifiedAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['modifiedAt'] as String),
|
||||
fileId: (json['fileId'] as num?)?.toInt(),
|
||||
hasPreview: json['hasPreview'] as bool?,
|
||||
isSharedWithMe: json['isSharedWithMe'] as bool?,
|
||||
);
|
||||
$checkedCreate('CacheableFile', json, ($checkedConvert) {
|
||||
final val = CacheableFile(
|
||||
path: $checkedConvert('path', (v) => v as String),
|
||||
isDirectory: $checkedConvert('isDirectory', (v) => v as bool),
|
||||
name: $checkedConvert('name', (v) => v as String),
|
||||
mimeType: $checkedConvert('mimeType', (v) => v as String?),
|
||||
size: $checkedConvert('size', (v) => (v as num?)?.toInt()),
|
||||
eTag: $checkedConvert('eTag', (v) => v as String?),
|
||||
createdAt: $checkedConvert(
|
||||
'createdAt',
|
||||
(v) => v == null ? null : DateTime.parse(v as String),
|
||||
),
|
||||
modifiedAt: $checkedConvert(
|
||||
'modifiedAt',
|
||||
(v) => v == null ? null : DateTime.parse(v as String),
|
||||
),
|
||||
fileId: $checkedConvert('fileId', (v) => (v as num?)?.toInt()),
|
||||
hasPreview: $checkedConvert('hasPreview', (v) => v as bool?),
|
||||
isSharedWithMe: $checkedConvert('isSharedWithMe', (v) => v as bool?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CacheableFileToJson(CacheableFile instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -7,7 +7,10 @@ part of 'list_files_params.dart';
|
||||
// **************************************************************************
|
||||
|
||||
ListFilesParams _$ListFilesParamsFromJson(Map<String, dynamic> json) =>
|
||||
ListFilesParams(json['path'] as String);
|
||||
$checkedCreate('ListFilesParams', json, ($checkedConvert) {
|
||||
final val = ListFilesParams($checkedConvert('path', (v) => v as String));
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$ListFilesParamsToJson(ListFilesParams instance) =>
|
||||
<String, dynamic>{'path': instance.path};
|
||||
@@ -7,14 +7,23 @@ part of 'list_files_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
ListFilesResponse _$ListFilesResponseFromJson(Map<String, dynamic> json) =>
|
||||
ListFilesResponse(
|
||||
(json['files'] as List<dynamic>)
|
||||
.map((e) => CacheableFile.fromJson(e as Map<String, dynamic>))
|
||||
.toSet(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('ListFilesResponse', json, ($checkedConvert) {
|
||||
final val = ListFilesResponse(
|
||||
$checkedConvert(
|
||||
'files',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => CacheableFile.fromJson(e as Map<String, dynamic>))
|
||||
.toSet(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$ListFilesResponseToJson(ListFilesResponse instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:nextcloud/nextcloud.dart';
|
||||
import '../../../model/endpoint_data.dart';
|
||||
import '../../../session/session_manager.dart';
|
||||
import '../../api_response.dart';
|
||||
import '../../demo/demo_mode.dart';
|
||||
import '../../errors/network_exception.dart';
|
||||
|
||||
abstract class WebdavApi<T> {
|
||||
T genericParams;
|
||||
@@ -18,6 +20,14 @@ abstract class WebdavApi<T> {
|
||||
/// changes (app password minted/renewed, account switch) so it never keeps
|
||||
/// authenticating with stale credentials.
|
||||
static Future<WebDavClient> get webdav {
|
||||
// The WebDAV client talks dart:io directly, past DemoHttpClient.
|
||||
if (DemoMode.active) {
|
||||
return Future.error(
|
||||
NetworkException.demoBlocked(
|
||||
Uri.parse(EndpointData().nextcloud().origin()),
|
||||
),
|
||||
);
|
||||
}
|
||||
// Keyed by user too: two accounts may share a password (no app password
|
||||
// minted), and the client would keep the previous login name.
|
||||
final nextcloud = SessionManager().requireNextcloud();
|
||||
@@ -31,7 +41,7 @@ abstract class WebdavApi<T> {
|
||||
|
||||
static Future<WebDavClient> establishWebdavConnection() async =>
|
||||
NextcloudClient(
|
||||
Uri.parse('https://${EndpointData().nextcloud().full()}'),
|
||||
Uri.parse(EndpointData().nextcloud().origin()),
|
||||
// App password preferred — with 2FA the real password is not accepted
|
||||
// by Nextcloud at all (see NextcloudCredentials.usesLoginFlow).
|
||||
password: SessionManager().requireNextcloud().secret,
|
||||
@@ -41,5 +51,5 @@ abstract class WebdavApi<T> {
|
||||
/// Builds the WebDAV download URL without embedded credentials. Callers must
|
||||
/// authenticate via the [NextcloudCredentials.authHeaders] header instead.
|
||||
static String buildWebdavUrl() =>
|
||||
'https://${EndpointData().nextcloud().full()}/remote.php/dav/files/${SessionManager().requireNextcloud().username}/';
|
||||
'${EndpointData().nextcloud().origin()}/remote.php/dav/files/${SessionManager().requireNextcloud().username}/';
|
||||
}
|
||||
@@ -81,8 +81,8 @@ class MarianumConnectTokenStorage {
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
await _storage.delete(key: _tokenKey);
|
||||
await _storage.delete(key: _tokenIdKey);
|
||||
await _storage.delete(key: _expiresAtKey);
|
||||
for (final key in fieldKeys) {
|
||||
await _storage.delete(key: key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
/// Platform wire value MarianumConnect expects (`android`/`ios`), null elsewhere.
|
||||
String? clientPlatform() {
|
||||
if (Platform.isAndroid) return 'android';
|
||||
if (Platform.isIOS) return 'ios';
|
||||
return null;
|
||||
}
|
||||
@@ -6,12 +6,22 @@ part of 'mc_holiday.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
McHoliday _$McHolidayFromJson(Map<String, dynamic> json) => McHoliday(
|
||||
shortName: json['shortName'] as String,
|
||||
longName: json['longName'] as String,
|
||||
startDate: McHoliday._dateFromJson(json['startDate'] as String),
|
||||
endDate: McHoliday._dateFromJson(json['endDate'] as String),
|
||||
);
|
||||
McHoliday _$McHolidayFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate('McHoliday', json, ($checkedConvert) {
|
||||
final val = McHoliday(
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
longName: $checkedConvert('longName', (v) => v as String),
|
||||
startDate: $checkedConvert(
|
||||
'startDate',
|
||||
(v) => McHoliday._dateFromJson(v as String),
|
||||
),
|
||||
endDate: $checkedConvert(
|
||||
'endDate',
|
||||
(v) => McHoliday._dateFromJson(v as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McHolidayToJson(McHoliday instance) => <String, dynamic>{
|
||||
'shortName': instance.shortName,
|
||||
|
||||
@@ -8,12 +8,15 @@ part of 'absence_prefill_response.dart';
|
||||
|
||||
AbsencePrefillResponse _$AbsencePrefillResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => AbsencePrefillResponse(
|
||||
firstName: json['firstName'] as String? ?? '',
|
||||
lastName: json['lastName'] as String? ?? '',
|
||||
className: json['className'] as String? ?? '',
|
||||
phone: json['phone'] as String? ?? '',
|
||||
);
|
||||
) => $checkedCreate('AbsencePrefillResponse', json, ($checkedConvert) {
|
||||
final val = AbsencePrefillResponse(
|
||||
firstName: $checkedConvert('firstName', (v) => v as String? ?? ''),
|
||||
lastName: $checkedConvert('lastName', (v) => v as String? ?? ''),
|
||||
className: $checkedConvert('className', (v) => v as String? ?? ''),
|
||||
phone: $checkedConvert('phone', (v) => v as String? ?? ''),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$AbsencePrefillResponseToJson(
|
||||
AbsencePrefillResponse instance,
|
||||
|
||||
@@ -7,14 +7,17 @@ part of 'auth_login_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
AuthLoginUser _$AuthLoginUserFromJson(Map<String, dynamic> json) =>
|
||||
AuthLoginUser(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
userType: json['userType'] as String?,
|
||||
className: json['className'] as String?,
|
||||
);
|
||||
$checkedCreate('AuthLoginUser', json, ($checkedConvert) {
|
||||
final val = AuthLoginUser(
|
||||
id: $checkedConvert('id', (v) => v as String),
|
||||
username: $checkedConvert('username', (v) => v as String),
|
||||
firstName: $checkedConvert('firstName', (v) => v as String),
|
||||
lastName: $checkedConvert('lastName', (v) => v as String),
|
||||
userType: $checkedConvert('userType', (v) => v as String?),
|
||||
className: $checkedConvert('className', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$AuthLoginUserToJson(AuthLoginUser instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -27,12 +30,21 @@ Map<String, dynamic> _$AuthLoginUserToJson(AuthLoginUser instance) =>
|
||||
};
|
||||
|
||||
AuthLoginResponse _$AuthLoginResponseFromJson(Map<String, dynamic> json) =>
|
||||
AuthLoginResponse(
|
||||
token: json['token'] as String,
|
||||
tokenId: json['tokenId'] as String,
|
||||
expiresAt: AuthLoginResponse._expiresFromJson(json['expiresAt']),
|
||||
user: AuthLoginUser.fromJson(json['user'] as Map<String, dynamic>),
|
||||
);
|
||||
$checkedCreate('AuthLoginResponse', json, ($checkedConvert) {
|
||||
final val = AuthLoginResponse(
|
||||
token: $checkedConvert('token', (v) => v as String),
|
||||
tokenId: $checkedConvert('tokenId', (v) => v as String),
|
||||
expiresAt: $checkedConvert(
|
||||
'expiresAt',
|
||||
(v) => AuthLoginResponse._expiresFromJson(v),
|
||||
),
|
||||
user: $checkedConvert(
|
||||
'user',
|
||||
(v) => AuthLoginUser.fromJson(v as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$AuthLoginResponseToJson(AuthLoginResponse instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -25,6 +25,7 @@ class GetBreakersResponse extends ApiResponse {
|
||||
|
||||
@JsonSerializable()
|
||||
class BreakerRule {
|
||||
@JsonKey(unknownEnumValue: BreakerArea.unknown)
|
||||
List<BreakerArea> areas;
|
||||
String message;
|
||||
|
||||
@@ -69,4 +70,7 @@ enum BreakerArea {
|
||||
feedback,
|
||||
@JsonValue('MORE')
|
||||
more,
|
||||
|
||||
/// An area the backend added after this build; no module maps to it.
|
||||
unknown,
|
||||
}
|
||||
@@ -7,15 +7,25 @@ part of 'get_breakers_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
GetBreakersResponse _$GetBreakersResponseFromJson(Map<String, dynamic> json) =>
|
||||
GetBreakersResponse(
|
||||
(json['rules'] as List<dynamic>?)
|
||||
?.map((e) => BreakerRule.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('GetBreakersResponse', json, ($checkedConvert) {
|
||||
final val = GetBreakersResponse(
|
||||
$checkedConvert(
|
||||
'rules',
|
||||
(v) =>
|
||||
(v as List<dynamic>?)
|
||||
?.map((e) => BreakerRule.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetBreakersResponseToJson(
|
||||
GetBreakersResponse instance,
|
||||
@@ -24,13 +34,26 @@ Map<String, dynamic> _$GetBreakersResponseToJson(
|
||||
'rules': instance.rules.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
||||
BreakerRule _$BreakerRuleFromJson(Map<String, dynamic> json) => BreakerRule(
|
||||
(json['areas'] as List<dynamic>)
|
||||
.map((e) => $enumDecode(_$BreakerAreaEnumMap, e))
|
||||
.toList(),
|
||||
json['message'] as String,
|
||||
(json['maxBuild'] as num?)?.toInt(),
|
||||
);
|
||||
BreakerRule _$BreakerRuleFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate('BreakerRule', json, ($checkedConvert) {
|
||||
final val = BreakerRule(
|
||||
$checkedConvert(
|
||||
'areas',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map(
|
||||
(e) => $enumDecode(
|
||||
_$BreakerAreaEnumMap,
|
||||
e,
|
||||
unknownValue: BreakerArea.unknown,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
$checkedConvert('message', (v) => v as String),
|
||||
$checkedConvert('maxBuild', (v) => (v as num?)?.toInt()),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$BreakerRuleToJson(BreakerRule instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -52,4 +75,5 @@ const _$BreakerAreaEnumMap = {
|
||||
BreakerArea.dates: 'DATES',
|
||||
BreakerArea.feedback: 'FEEDBACK',
|
||||
BreakerArea.more: 'MORE',
|
||||
BreakerArea.unknown: 'unknown',
|
||||
};
|
||||
@@ -8,13 +8,28 @@ part of 'get_capabilities_response.dart';
|
||||
|
||||
CapabilitiesResponse _$CapabilitiesResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => CapabilitiesResponse(
|
||||
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
||||
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
||||
timetablePastDays: (json['timetablePastDays'] as num?)?.toInt(),
|
||||
timetableFutureDays: (json['timetableFutureDays'] as num?)?.toInt(),
|
||||
userType: json['userType'] as String?,
|
||||
);
|
||||
) => $checkedCreate('CapabilitiesResponse', json, ($checkedConvert) {
|
||||
final val = CapabilitiesResponse(
|
||||
viewForeignTimetables: $checkedConvert(
|
||||
'viewForeignTimetables',
|
||||
(v) => v as bool? ?? false,
|
||||
),
|
||||
pushNotifications: $checkedConvert(
|
||||
'pushNotifications',
|
||||
(v) => v as bool? ?? false,
|
||||
),
|
||||
timetablePastDays: $checkedConvert(
|
||||
'timetablePastDays',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
timetableFutureDays: $checkedConvert(
|
||||
'timetableFutureDays',
|
||||
(v) => (v as num?)?.toInt(),
|
||||
),
|
||||
userType: $checkedConvert('userType', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CapabilitiesResponseToJson(
|
||||
CapabilitiesResponse instance,
|
||||
|
||||
@@ -7,14 +7,20 @@ part of 'get_ticker_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
TickerResponse _$TickerResponseFromJson(Map<String, dynamic> json) =>
|
||||
TickerResponse(
|
||||
schemaVersion: (json['schemaVersion'] as num?)?.toInt() ?? 1,
|
||||
available: json['available'] as bool? ?? false,
|
||||
hash: json['hash'] as String?,
|
||||
publishedAt: json['publishedAt'] as String?,
|
||||
webUrl: json['webUrl'] as String? ?? '/ticker',
|
||||
content: json['content'] as Map<String, dynamic>?,
|
||||
);
|
||||
$checkedCreate('TickerResponse', json, ($checkedConvert) {
|
||||
final val = TickerResponse(
|
||||
schemaVersion: $checkedConvert(
|
||||
'schemaVersion',
|
||||
(v) => (v as num?)?.toInt() ?? 1,
|
||||
),
|
||||
available: $checkedConvert('available', (v) => v as bool? ?? false),
|
||||
hash: $checkedConvert('hash', (v) => v as String?),
|
||||
publishedAt: $checkedConvert('publishedAt', (v) => v as String?),
|
||||
webUrl: $checkedConvert('webUrl', (v) => v as String? ?? '/ticker'),
|
||||
content: $checkedConvert('content', (v) => v as Map<String, dynamic>?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TickerResponseToJson(TickerResponse instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -7,15 +7,26 @@ part of 'get_ticker_nav_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
TickerNavResponse _$TickerNavResponseFromJson(Map<String, dynamic> json) =>
|
||||
TickerNavResponse(
|
||||
schemaVersion: (json['schemaVersion'] as num?)?.toInt() ?? 1,
|
||||
navHash: json['navHash'] as String? ?? '',
|
||||
sections:
|
||||
(json['sections'] as List<dynamic>?)
|
||||
?.map((e) => TickerNavSection.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
$checkedCreate('TickerNavResponse', json, ($checkedConvert) {
|
||||
final val = TickerNavResponse(
|
||||
schemaVersion: $checkedConvert(
|
||||
'schemaVersion',
|
||||
(v) => (v as num?)?.toInt() ?? 1,
|
||||
),
|
||||
navHash: $checkedConvert('navHash', (v) => v as String? ?? ''),
|
||||
sections: $checkedConvert(
|
||||
'sections',
|
||||
(v) =>
|
||||
(v as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => TickerNavSection.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TickerNavResponseToJson(TickerNavResponse instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -25,14 +36,22 @@ Map<String, dynamic> _$TickerNavResponseToJson(TickerNavResponse instance) =>
|
||||
};
|
||||
|
||||
TickerNavSection _$TickerNavSectionFromJson(Map<String, dynamic> json) =>
|
||||
TickerNavSection(
|
||||
title: json['title'] as String? ?? '',
|
||||
pages:
|
||||
(json['pages'] as List<dynamic>?)
|
||||
?.map((e) => TickerNavPage.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
$checkedCreate('TickerNavSection', json, ($checkedConvert) {
|
||||
final val = TickerNavSection(
|
||||
title: $checkedConvert('title', (v) => v as String? ?? ''),
|
||||
pages: $checkedConvert(
|
||||
'pages',
|
||||
(v) =>
|
||||
(v as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => TickerNavPage.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList() ??
|
||||
[],
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TickerNavSectionToJson(TickerNavSection instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -41,15 +60,21 @@ Map<String, dynamic> _$TickerNavSectionToJson(TickerNavSection instance) =>
|
||||
};
|
||||
|
||||
TickerNavPage _$TickerNavPageFromJson(Map<String, dynamic> json) =>
|
||||
TickerNavPage(
|
||||
title: json['title'] as String? ?? '',
|
||||
slug: json['slug'] as String? ?? '',
|
||||
kind: json['kind'] as String? ?? 'CONTENT',
|
||||
hash: json['hash'] as String?,
|
||||
externalUrl: json['externalUrl'] as String?,
|
||||
externalUrlNewTab: json['externalUrlNewTab'] as bool?,
|
||||
fileUrl: json['fileUrl'] as String?,
|
||||
);
|
||||
$checkedCreate('TickerNavPage', json, ($checkedConvert) {
|
||||
final val = TickerNavPage(
|
||||
title: $checkedConvert('title', (v) => v as String? ?? ''),
|
||||
slug: $checkedConvert('slug', (v) => v as String? ?? ''),
|
||||
kind: $checkedConvert('kind', (v) => v as String? ?? 'CONTENT'),
|
||||
hash: $checkedConvert('hash', (v) => v as String?),
|
||||
externalUrl: $checkedConvert('externalUrl', (v) => v as String?),
|
||||
externalUrlNewTab: $checkedConvert(
|
||||
'externalUrlNewTab',
|
||||
(v) => v as bool?,
|
||||
),
|
||||
fileUrl: $checkedConvert('fileUrl', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TickerNavPageToJson(TickerNavPage instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../errors/app_exception.dart';
|
||||
import '../../../errors/ticker_content_unavailable_exception.dart';
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_query.dart';
|
||||
import 'get_ticker_page_response.dart';
|
||||
|
||||
@@ -14,26 +14,20 @@ import 'get_ticker_page_response.dart';
|
||||
/// 404 `{ "error": "CONTENT_UNAVAILABLE", "webUrl": ... }`; that case is mapped
|
||||
/// to a dedicated [TickerContentUnavailableException] carrying the browser
|
||||
/// fallback URL, so the detail screen can offer "open in browser" instead of a
|
||||
/// generic error. The bespoke 404 handling is why this keeps its own try/catch
|
||||
/// instead of the base [guard].
|
||||
/// generic error.
|
||||
class GetTickerPage extends MarianumConnectQuery {
|
||||
final String slug;
|
||||
|
||||
GetTickerPage(this.slug, {super.dio});
|
||||
|
||||
Future<TickerPageResponse> run() async {
|
||||
try {
|
||||
final response = await dio.get<Map<String, dynamic>>(
|
||||
endpoint('ticker/pages/${Uri.encodeComponent(slug)}'),
|
||||
);
|
||||
return TickerPageResponse.fromJson(response.data!);
|
||||
} on DioException catch (e) {
|
||||
throw _mapError(e);
|
||||
}
|
||||
}
|
||||
Future<TickerPageResponse> run() => getObject(
|
||||
'ticker/pages/${Uri.encodeComponent(slug)}',
|
||||
TickerPageResponse.fromJson,
|
||||
);
|
||||
|
||||
Object _mapError(DioException e) {
|
||||
final response = e.response;
|
||||
@override
|
||||
AppException mapError(DioException error) {
|
||||
final response = error.response;
|
||||
if (response?.statusCode == 404) {
|
||||
final body = _asMap(response?.data);
|
||||
if (body != null && body['error'] == 'CONTENT_UNAVAILABLE') {
|
||||
@@ -44,7 +38,7 @@ class GetTickerPage extends MarianumConnectQuery {
|
||||
);
|
||||
}
|
||||
}
|
||||
return mapMarianumConnectError(e);
|
||||
return super.mapError(error);
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _asMap(dynamic data) {
|
||||
|
||||
@@ -7,22 +7,31 @@ part of 'get_ticker_page_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
TickerPageResponse _$TickerPageResponseFromJson(Map<String, dynamic> json) =>
|
||||
TickerPageResponse(
|
||||
schemaVersion: (json['schemaVersion'] as num?)?.toInt() ?? 1,
|
||||
slug: json['slug'] as String?,
|
||||
title: json['title'] as String?,
|
||||
kind: json['kind'] as String? ?? 'CONTENT',
|
||||
content: json['content'] as Map<String, dynamic>?,
|
||||
externalUrl: json['externalUrl'] as String?,
|
||||
externalUrlNewTab: json['externalUrlNewTab'] as bool?,
|
||||
fileUrl: json['fileUrl'] as String?,
|
||||
contentType: json['contentType'] as String?,
|
||||
filename: json['filename'] as String?,
|
||||
hash: json['hash'] as String?,
|
||||
webUrl: json['webUrl'] as String?,
|
||||
publishedAt: json['publishedAt'] as String?,
|
||||
fileFetchedAt: json['fileFetchedAt'] as String?,
|
||||
);
|
||||
$checkedCreate('TickerPageResponse', json, ($checkedConvert) {
|
||||
final val = TickerPageResponse(
|
||||
schemaVersion: $checkedConvert(
|
||||
'schemaVersion',
|
||||
(v) => (v as num?)?.toInt() ?? 1,
|
||||
),
|
||||
slug: $checkedConvert('slug', (v) => v as String?),
|
||||
title: $checkedConvert('title', (v) => v as String?),
|
||||
kind: $checkedConvert('kind', (v) => v as String? ?? 'CONTENT'),
|
||||
content: $checkedConvert('content', (v) => v as Map<String, dynamic>?),
|
||||
externalUrl: $checkedConvert('externalUrl', (v) => v as String?),
|
||||
externalUrlNewTab: $checkedConvert(
|
||||
'externalUrlNewTab',
|
||||
(v) => v as bool?,
|
||||
),
|
||||
fileUrl: $checkedConvert('fileUrl', (v) => v as String?),
|
||||
contentType: $checkedConvert('contentType', (v) => v as String?),
|
||||
filename: $checkedConvert('filename', (v) => v as String?),
|
||||
hash: $checkedConvert('hash', (v) => v as String?),
|
||||
webUrl: $checkedConvert('webUrl', (v) => v as String?),
|
||||
publishedAt: $checkedConvert('publishedAt', (v) => v as String?),
|
||||
fileFetchedAt: $checkedConvert('fileFetchedAt', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TickerPageResponseToJson(TickerPageResponse instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import '../../client_platform.dart';
|
||||
import 'report_client_error.dart';
|
||||
|
||||
/// Fire-and-forget bridge from the app's global error handlers to the
|
||||
@@ -36,6 +36,15 @@ class ClientErrorReporter {
|
||||
);
|
||||
}
|
||||
|
||||
/// Server entries the decoder could not map (see `decodeListLenient`): the
|
||||
/// app keeps working without them, the report says which field drifted.
|
||||
static void reportDecodeWarnings(String context, Iterable<String> warnings) {
|
||||
for (final warning in warnings) {
|
||||
log('$context: skipped entry: $warning');
|
||||
_report(errorType: 'decode', message: warning, context: context);
|
||||
}
|
||||
}
|
||||
|
||||
static void _report({
|
||||
required String errorType,
|
||||
required String message,
|
||||
@@ -61,7 +70,7 @@ class ClientErrorReporter {
|
||||
message: message,
|
||||
stacktrace: stacktrace,
|
||||
context: context,
|
||||
platform: _platform(),
|
||||
platform: clientPlatform(),
|
||||
appVersion: _appVersion,
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -73,10 +82,4 @@ class ClientErrorReporter {
|
||||
final normalized = message.toLowerCase().replaceAll(RegExp(r'\d+'), '#');
|
||||
return '$errorType|$normalized|${context ?? ''}';
|
||||
}
|
||||
|
||||
static String? _platform() {
|
||||
if (Platform.isAndroid) return 'android';
|
||||
if (Platform.isIOS) return 'ios';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'dart:typed_data';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import '../../client_platform.dart';
|
||||
import '../../marianumconnect_query.dart';
|
||||
|
||||
/// Submits user feedback to MarianumConnect (`POST me/feedback`, bearer-auth).
|
||||
@@ -27,7 +28,7 @@ class SubmitFeedback extends MarianumConnectQuery {
|
||||
'message': message,
|
||||
'screenshot': ?screenshotBase64,
|
||||
'screenshotContentType': screenshot != null ? screenshotContentType : null,
|
||||
'platform': _platform(),
|
||||
'platform': clientPlatform(),
|
||||
'appVersion': package.version,
|
||||
'appBuild': int.tryParse(package.buildNumber),
|
||||
'deviceModel': await _deviceModel(),
|
||||
@@ -35,12 +36,6 @@ class SubmitFeedback extends MarianumConnectQuery {
|
||||
);
|
||||
});
|
||||
|
||||
static String? _platform() {
|
||||
if (Platform.isAndroid) return 'android';
|
||||
if (Platform.isIOS) return 'ios';
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<String?> _deviceModel() async {
|
||||
try {
|
||||
final info = DeviceInfoPlugin();
|
||||
|
||||
+25
-14
@@ -7,11 +7,14 @@ part of 'timetable_get_classes_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
McTimetableClass _$McTimetableClassFromJson(Map<String, dynamic> json) =>
|
||||
McTimetableClass(
|
||||
id: (json['id'] as num).toInt(),
|
||||
shortName: json['shortName'] as String,
|
||||
longName: json['longName'] as String,
|
||||
);
|
||||
$checkedCreate('McTimetableClass', json, ($checkedConvert) {
|
||||
final val = McTimetableClass(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
longName: $checkedConvert('longName', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimetableClassToJson(McTimetableClass instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -22,15 +25,23 @@ Map<String, dynamic> _$McTimetableClassToJson(McTimetableClass instance) =>
|
||||
|
||||
TimetableGetClassesResponse _$TimetableGetClassesResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetClassesResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McTimetableClass.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetClassesResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetClassesResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McTimetableClass.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetClassesResponseToJson(
|
||||
TimetableGetClassesResponse instance,
|
||||
|
||||
+17
-9
@@ -8,15 +8,23 @@ part of 'timetable_get_holidays_response.dart';
|
||||
|
||||
TimetableGetHolidaysResponse _$TimetableGetHolidaysResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetHolidaysResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McHoliday.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetHolidaysResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetHolidaysResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McHoliday.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetHolidaysResponseToJson(
|
||||
TimetableGetHolidaysResponse instance,
|
||||
|
||||
+26
-14
@@ -6,11 +6,15 @@ part of 'timetable_get_rooms_response.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
McRoom _$McRoomFromJson(Map<String, dynamic> json) => McRoom(
|
||||
id: (json['id'] as num).toInt(),
|
||||
shortName: json['shortName'] as String,
|
||||
longName: json['longName'] as String,
|
||||
);
|
||||
McRoom _$McRoomFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate('McRoom', json, ($checkedConvert) {
|
||||
final val = McRoom(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
longName: $checkedConvert('longName', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McRoomToJson(McRoom instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
@@ -20,15 +24,23 @@ Map<String, dynamic> _$McRoomToJson(McRoom instance) => <String, dynamic>{
|
||||
|
||||
TimetableGetRoomsResponse _$TimetableGetRoomsResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetRoomsResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McRoom.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetRoomsResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetRoomsResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McRoom.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetRoomsResponseToJson(
|
||||
TimetableGetRoomsResponse instance,
|
||||
|
||||
+21
-14
@@ -8,20 +8,27 @@ part of 'timetable_get_schoolyear_response.dart';
|
||||
|
||||
TimetableGetSchoolyearResponse _$TimetableGetSchoolyearResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetSchoolyearResponse(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String,
|
||||
startDate: TimetableGetSchoolyearResponse._dateFromJson(
|
||||
json['startDate'] as String,
|
||||
),
|
||||
endDate: TimetableGetSchoolyearResponse._dateFromJson(
|
||||
json['endDate'] as String,
|
||||
),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetSchoolyearResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetSchoolyearResponse(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
name: $checkedConvert('name', (v) => v as String),
|
||||
startDate: $checkedConvert(
|
||||
'startDate',
|
||||
(v) => TimetableGetSchoolyearResponse._dateFromJson(v as String),
|
||||
),
|
||||
endDate: $checkedConvert(
|
||||
'endDate',
|
||||
(v) => TimetableGetSchoolyearResponse._dateFromJson(v as String),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetSchoolyearResponseToJson(
|
||||
TimetableGetSchoolyearResponse instance,
|
||||
|
||||
+26
-15
@@ -7,12 +7,15 @@ part of 'timetable_get_students_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
McTimetableStudent _$McTimetableStudentFromJson(Map<String, dynamic> json) =>
|
||||
McTimetableStudent(
|
||||
id: (json['id'] as num).toInt(),
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
);
|
||||
$checkedCreate('McTimetableStudent', json, ($checkedConvert) {
|
||||
final val = McTimetableStudent(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
firstName: $checkedConvert('firstName', (v) => v as String),
|
||||
lastName: $checkedConvert('lastName', (v) => v as String),
|
||||
displayName: $checkedConvert('displayName', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimetableStudentToJson(McTimetableStudent instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -24,15 +27,23 @@ Map<String, dynamic> _$McTimetableStudentToJson(McTimetableStudent instance) =>
|
||||
|
||||
TimetableGetStudentsResponse _$TimetableGetStudentsResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetStudentsResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McTimetableStudent.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetStudentsResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetStudentsResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McTimetableStudent.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetStudentsResponseToJson(
|
||||
TimetableGetStudentsResponse instance,
|
||||
|
||||
+27
-15
@@ -6,12 +6,16 @@ part of 'timetable_get_subjects_response.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
McSubject _$McSubjectFromJson(Map<String, dynamic> json) => McSubject(
|
||||
id: (json['id'] as num).toInt(),
|
||||
shortName: json['shortName'] as String,
|
||||
longName: json['longName'] as String,
|
||||
color: json['color'] as String?,
|
||||
);
|
||||
McSubject _$McSubjectFromJson(Map<String, dynamic> json) =>
|
||||
$checkedCreate('McSubject', json, ($checkedConvert) {
|
||||
final val = McSubject(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
longName: $checkedConvert('longName', (v) => v as String),
|
||||
color: $checkedConvert('color', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McSubjectToJson(McSubject instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
@@ -22,15 +26,23 @@ Map<String, dynamic> _$McSubjectToJson(McSubject instance) => <String, dynamic>{
|
||||
|
||||
TimetableGetSubjectsResponse _$TimetableGetSubjectsResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetSubjectsResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McSubject.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetSubjectsResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetSubjectsResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McSubject.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetSubjectsResponseToJson(
|
||||
TimetableGetSubjectsResponse instance,
|
||||
|
||||
+28
-17
@@ -8,11 +8,14 @@ part of 'timetable_get_teachers_response.dart';
|
||||
|
||||
McTimetableTeacherElement _$McTimetableTeacherElementFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => McTimetableTeacherElement(
|
||||
id: (json['id'] as num).toInt(),
|
||||
shortName: json['shortName'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
);
|
||||
) => $checkedCreate('McTimetableTeacherElement', json, ($checkedConvert) {
|
||||
final val = McTimetableTeacherElement(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
displayName: $checkedConvert('displayName', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimetableTeacherElementToJson(
|
||||
McTimetableTeacherElement instance,
|
||||
@@ -24,18 +27,26 @@ Map<String, dynamic> _$McTimetableTeacherElementToJson(
|
||||
|
||||
TimetableGetTeachersResponse _$TimetableGetTeachersResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetTeachersResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map(
|
||||
(e) =>
|
||||
McTimetableTeacherElement.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetTeachersResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetTeachersResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map(
|
||||
(e) =>
|
||||
McTimetableTeacherElement.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetTeachersResponseToJson(
|
||||
TimetableGetTeachersResponse instance,
|
||||
|
||||
+35
-15
@@ -7,12 +7,24 @@ part of 'timetable_get_timegrid_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
McTimegridUnit _$McTimegridUnitFromJson(Map<String, dynamic> json) =>
|
||||
McTimegridUnit(
|
||||
dayOfWeek: _dayFromJson(json['dayOfWeek'] as String),
|
||||
label: json['label'] as String,
|
||||
startTime: McTimegridUnit._timeFromJson(json['startTime'] as String),
|
||||
endTime: McTimegridUnit._timeFromJson(json['endTime'] as String),
|
||||
);
|
||||
$checkedCreate('McTimegridUnit', json, ($checkedConvert) {
|
||||
final val = McTimegridUnit(
|
||||
dayOfWeek: $checkedConvert(
|
||||
'dayOfWeek',
|
||||
(v) => _dayFromJson(v as String),
|
||||
),
|
||||
label: $checkedConvert('label', (v) => v as String),
|
||||
startTime: $checkedConvert(
|
||||
'startTime',
|
||||
(v) => McTimegridUnit._timeFromJson(v as String),
|
||||
),
|
||||
endTime: $checkedConvert(
|
||||
'endTime',
|
||||
(v) => McTimegridUnit._timeFromJson(v as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimegridUnitToJson(McTimegridUnit instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -24,15 +36,23 @@ Map<String, dynamic> _$McTimegridUnitToJson(McTimegridUnit instance) =>
|
||||
|
||||
TimetableGetTimegridResponse _$TimetableGetTimegridResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetTimegridResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McTimegridUnit.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetTimegridResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetTimegridResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McTimegridUnit.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetTimegridResponseToJson(
|
||||
TimetableGetTimegridResponse instance,
|
||||
|
||||
+87
-42
@@ -7,14 +7,23 @@ part of 'timetable_get_week_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
McTimetableTeacher _$McTimetableTeacherFromJson(Map<String, dynamic> json) =>
|
||||
McTimetableTeacher(
|
||||
shortName: json['shortName'] as String,
|
||||
displayName: json['displayName'] as String,
|
||||
originalShortName: json['originalShortName'] as String?,
|
||||
originalDisplayName: json['originalDisplayName'] as String?,
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
originalId: (json['originalId'] as num?)?.toInt(),
|
||||
);
|
||||
$checkedCreate('McTimetableTeacher', json, ($checkedConvert) {
|
||||
final val = McTimetableTeacher(
|
||||
shortName: $checkedConvert('shortName', (v) => v as String),
|
||||
displayName: $checkedConvert('displayName', (v) => v as String),
|
||||
originalShortName: $checkedConvert(
|
||||
'originalShortName',
|
||||
(v) => v as String?,
|
||||
),
|
||||
originalDisplayName: $checkedConvert(
|
||||
'originalDisplayName',
|
||||
(v) => v as String?,
|
||||
),
|
||||
id: $checkedConvert('id', (v) => (v as num?)?.toInt()),
|
||||
originalId: $checkedConvert('originalId', (v) => (v as num?)?.toInt()),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimetableTeacherToJson(McTimetableTeacher instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -26,31 +35,53 @@ Map<String, dynamic> _$McTimetableTeacherToJson(McTimetableTeacher instance) =>
|
||||
'originalId': instance.originalId,
|
||||
};
|
||||
|
||||
McTimetableEntry _$McTimetableEntryFromJson(Map<String, dynamic> json) =>
|
||||
McTimetableEntry(
|
||||
id: (json['id'] as num).toInt(),
|
||||
date: McTimetableEntry._dateFromJson(json['date'] as String),
|
||||
startTime: McTimetableEntry._timeFromJson(json['startTime'] as String),
|
||||
endTime: McTimetableEntry._timeFromJson(json['endTime'] as String),
|
||||
subjects: (json['subjects'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
teachers: (json['teachers'] as List<dynamic>)
|
||||
McTimetableEntry _$McTimetableEntryFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => $checkedCreate('McTimetableEntry', json, ($checkedConvert) {
|
||||
final val = McTimetableEntry(
|
||||
id: $checkedConvert('id', (v) => (v as num).toInt()),
|
||||
date: $checkedConvert(
|
||||
'date',
|
||||
(v) => McTimetableEntry._dateFromJson(v as String),
|
||||
),
|
||||
startTime: $checkedConvert(
|
||||
'startTime',
|
||||
(v) => McTimetableEntry._timeFromJson(v as String),
|
||||
),
|
||||
endTime: $checkedConvert(
|
||||
'endTime',
|
||||
(v) => McTimetableEntry._timeFromJson(v as String),
|
||||
),
|
||||
subjects: $checkedConvert(
|
||||
'subjects',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
teachers: $checkedConvert(
|
||||
'teachers',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McTimetableTeacher.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
rooms: (json['rooms'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
classNames: (json['classNames'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
lessonType: json['lessonType'] as String,
|
||||
status: json['status'] as String,
|
||||
substitutionText: json['substitutionText'] as String?,
|
||||
lessonText: json['lessonText'] as String?,
|
||||
infoText: json['infoText'] as String?,
|
||||
classIds: (json['classIds'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
),
|
||||
rooms: $checkedConvert(
|
||||
'rooms',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
classNames: $checkedConvert(
|
||||
'classNames',
|
||||
(v) => (v as List<dynamic>).map((e) => e as String).toList(),
|
||||
),
|
||||
lessonType: $checkedConvert('lessonType', (v) => v as String),
|
||||
status: $checkedConvert('status', (v) => v as String),
|
||||
substitutionText: $checkedConvert('substitutionText', (v) => v as String?),
|
||||
lessonText: $checkedConvert('lessonText', (v) => v as String?),
|
||||
infoText: $checkedConvert('infoText', (v) => v as String?),
|
||||
classIds: $checkedConvert(
|
||||
'classIds',
|
||||
(v) => (v as List<dynamic>?)?.map((e) => (e as num).toInt()).toList(),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McTimetableEntryToJson(McTimetableEntry instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -72,17 +103,31 @@ Map<String, dynamic> _$McTimetableEntryToJson(McTimetableEntry instance) =>
|
||||
|
||||
TimetableGetWeekResponse _$TimetableGetWeekResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
TimetableGetWeekResponse(
|
||||
from: McTimetableEntry._dateFromJson(json['from'] as String),
|
||||
until: McTimetableEntry._dateFromJson(json['until'] as String),
|
||||
entries: (json['entries'] as List<dynamic>)
|
||||
.map((e) => McTimetableEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('TimetableGetWeekResponse', json, ($checkedConvert) {
|
||||
final val = TimetableGetWeekResponse(
|
||||
from: $checkedConvert(
|
||||
'from',
|
||||
(v) => McTimetableEntry._dateFromJson(v as String),
|
||||
),
|
||||
until: $checkedConvert(
|
||||
'until',
|
||||
(v) => McTimetableEntry._dateFromJson(v as String),
|
||||
),
|
||||
entries: $checkedConvert(
|
||||
'entries',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => McTimetableEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$TimetableGetWeekResponseToJson(
|
||||
TimetableGetWeekResponse instance,
|
||||
|
||||
@@ -7,13 +7,16 @@ part of 'user_search_response.dart';
|
||||
// **************************************************************************
|
||||
|
||||
McUserSearchResult _$McUserSearchResultFromJson(Map<String, dynamic> json) =>
|
||||
McUserSearchResult(
|
||||
username: json['username'] as String,
|
||||
firstName: json['firstName'] as String,
|
||||
lastName: json['lastName'] as String,
|
||||
userType: json['userType'] as String,
|
||||
className: json['className'] as String?,
|
||||
);
|
||||
$checkedCreate('McUserSearchResult', json, ($checkedConvert) {
|
||||
final val = McUserSearchResult(
|
||||
username: $checkedConvert('username', (v) => v as String),
|
||||
firstName: $checkedConvert('firstName', (v) => v as String),
|
||||
lastName: $checkedConvert('lastName', (v) => v as String),
|
||||
userType: $checkedConvert('userType', (v) => v as String),
|
||||
className: $checkedConvert('className', (v) => v as String?),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$McUserSearchResultToJson(McUserSearchResult instance) =>
|
||||
<String, dynamic>{
|
||||
@@ -25,14 +28,25 @@ Map<String, dynamic> _$McUserSearchResultToJson(McUserSearchResult instance) =>
|
||||
};
|
||||
|
||||
UserSearchResponse _$UserSearchResponseFromJson(Map<String, dynamic> json) =>
|
||||
UserSearchResponse(
|
||||
result: (json['result'] as List<dynamic>)
|
||||
.map((e) => McUserSearchResult.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
$checkedCreate('UserSearchResponse', json, ($checkedConvert) {
|
||||
final val = UserSearchResponse(
|
||||
result: $checkedConvert(
|
||||
'result',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map(
|
||||
(e) => McUserSearchResult.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$UserSearchResponseToJson(UserSearchResponse instance) =>
|
||||
<String, dynamic>{
|
||||
|
||||
@@ -8,18 +8,33 @@ part of 'custom_timetable_event.dart';
|
||||
|
||||
CustomTimetableEvent _$CustomTimetableEventFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => CustomTimetableEvent(
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String,
|
||||
startDate: CustomTimetableEvent._dateFromJson(json['startDate'] as String),
|
||||
endDate: CustomTimetableEvent._dateFromJson(json['endDate'] as String),
|
||||
color: json['color'] as String?,
|
||||
rrule: json['rrule'] as String,
|
||||
skipHolidays: json['skipHolidays'] as bool? ?? false,
|
||||
createdAt: CustomTimetableEvent._dateFromJson(json['createdAt'] as String),
|
||||
updatedAt: CustomTimetableEvent._dateFromJson(json['updatedAt'] as String),
|
||||
);
|
||||
) => $checkedCreate('CustomTimetableEvent', json, ($checkedConvert) {
|
||||
final val = CustomTimetableEvent(
|
||||
id: $checkedConvert('id', (v) => v as String),
|
||||
title: $checkedConvert('title', (v) => v as String),
|
||||
description: $checkedConvert('description', (v) => v as String),
|
||||
startDate: $checkedConvert(
|
||||
'startDate',
|
||||
(v) => CustomTimetableEvent._dateFromJson(v as String),
|
||||
),
|
||||
endDate: $checkedConvert(
|
||||
'endDate',
|
||||
(v) => CustomTimetableEvent._dateFromJson(v as String),
|
||||
),
|
||||
color: $checkedConvert('color', (v) => v as String?),
|
||||
rrule: $checkedConvert('rrule', (v) => v as String),
|
||||
skipHolidays: $checkedConvert('skipHolidays', (v) => v as bool? ?? false),
|
||||
createdAt: $checkedConvert(
|
||||
'createdAt',
|
||||
(v) => CustomTimetableEvent._dateFromJson(v as String),
|
||||
),
|
||||
updatedAt: $checkedConvert(
|
||||
'updatedAt',
|
||||
(v) => CustomTimetableEvent._dateFromJson(v as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$CustomTimetableEventToJson(
|
||||
CustomTimetableEvent instance,
|
||||
|
||||
@@ -8,7 +8,12 @@ part of 'get_custom_timetable_event_params.dart';
|
||||
|
||||
GetCustomTimetableEventParams _$GetCustomTimetableEventParamsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => GetCustomTimetableEventParams(json['user'] as String);
|
||||
) => $checkedCreate('GetCustomTimetableEventParams', json, ($checkedConvert) {
|
||||
final val = GetCustomTimetableEventParams(
|
||||
$checkedConvert('user', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetCustomTimetableEventParamsToJson(
|
||||
GetCustomTimetableEventParams instance,
|
||||
|
||||
+17
-11
@@ -8,17 +8,23 @@ part of 'get_custom_timetable_event_response.dart';
|
||||
|
||||
GetCustomTimetableEventResponse _$GetCustomTimetableEventResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) =>
|
||||
GetCustomTimetableEventResponse(
|
||||
(json['events'] as List<dynamic>)
|
||||
.map(
|
||||
(e) => CustomTimetableEvent.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
);
|
||||
) => $checkedCreate('GetCustomTimetableEventResponse', json, ($checkedConvert) {
|
||||
final val = GetCustomTimetableEventResponse(
|
||||
$checkedConvert(
|
||||
'events',
|
||||
(v) => (v as List<dynamic>)
|
||||
.map((e) => CustomTimetableEvent.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
$checkedConvert(
|
||||
'headers',
|
||||
(v) => val.headers = (v as Map<String, dynamic>?)?.map(
|
||||
(k, e) => MapEntry(k, e as String),
|
||||
),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$GetCustomTimetableEventResponseToJson(
|
||||
GetCustomTimetableEventResponse instance,
|
||||
|
||||
+7
-1
@@ -8,7 +8,13 @@ part of 'remove_custom_timetable_event_params.dart';
|
||||
|
||||
RemoveCustomTimetableEventParams _$RemoveCustomTimetableEventParamsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => RemoveCustomTimetableEventParams(json['id'] as String);
|
||||
) =>
|
||||
$checkedCreate('RemoveCustomTimetableEventParams', json, ($checkedConvert) {
|
||||
final val = RemoveCustomTimetableEventParams(
|
||||
$checkedConvert('id', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
});
|
||||
|
||||
Map<String, dynamic> _$RemoveCustomTimetableEventParamsToJson(
|
||||
RemoveCustomTimetableEventParams instance,
|
||||
|
||||
+30
-35
@@ -80,6 +80,25 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shares one debounce key, so a later emit supersedes a pending publish.
|
||||
void _scheduleWidgetPublish(TimetableState data, Duration delay) {
|
||||
final settingsCubit = context.read<SettingsCubit>();
|
||||
final capabilitiesCubit = context.read<CapabilitiesCubit>();
|
||||
final epoch = SessionManager().sessionEpoch;
|
||||
Debouncer.debounce(
|
||||
'widgetPublish',
|
||||
delay,
|
||||
() => unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
data,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
epoch: epoch,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _reportTelemetry() {
|
||||
if (!mounted) return;
|
||||
TelemetryHeartbeat.report(
|
||||
@@ -125,13 +144,18 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
final pending = await WidgetNavigation.consumePendingTimetableTap();
|
||||
if (!pending || !mounted) return;
|
||||
// `withNavBar: false` routes sit on the root navigator above the
|
||||
// bottom-nav; pop them so jumpToTab is actually visible. Stop at
|
||||
// popups so open dialogs/sheets stay alive.
|
||||
// bottom-nav; pop them so jumpToTab is actually visible.
|
||||
_popToShell();
|
||||
AppRoutes.goToTab(context, Modules.timetable);
|
||||
}
|
||||
|
||||
/// Pops full-page routes back to the tab shell, stopping at popups so open
|
||||
/// dialogs/sheets stay alive.
|
||||
void _popToShell() {
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
|
||||
}
|
||||
AppRoutes.goToTab(context, Modules.timetable);
|
||||
}
|
||||
|
||||
void _handlePendingShare() {
|
||||
@@ -140,10 +164,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
if (share == null || ShareIntentListener.instance.isShown(share)) return;
|
||||
// A second share would otherwise leave the previous share-flow page
|
||||
// on top with stale (already-cleared) file paths.
|
||||
final navigator = Navigator.of(context);
|
||||
if (navigator.canPop()) {
|
||||
navigator.popUntil((route) => route.isFirst || route is PopupRoute);
|
||||
}
|
||||
_popToShell();
|
||||
ShareIntentListener.instance.markShown(share);
|
||||
AppRoutes.openShareTarget(context, share);
|
||||
}
|
||||
@@ -166,27 +187,13 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
timetable.refresh();
|
||||
// Mirror BLoC updates into the home-screen widget without waiting
|
||||
// for the periodic background refresh.
|
||||
final settingsCubit = context.read<SettingsCubit>();
|
||||
final capabilitiesCubit = context.read<CapabilitiesCubit>();
|
||||
_timetableWidgetSync?.cancel();
|
||||
// Debounced: a refresh or week swipe emits several times in a row, and
|
||||
// only the settled state is worth mirroring.
|
||||
_timetableWidgetSync = timetable.stream.listen((state) {
|
||||
final data = state.data;
|
||||
if (data is TimetableState && !state.isLoading) {
|
||||
final epoch = SessionManager().sessionEpoch;
|
||||
Debouncer.debounce(
|
||||
'widgetPublish',
|
||||
const Duration(seconds: 1),
|
||||
() => unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
data,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
epoch: epoch,
|
||||
),
|
||||
),
|
||||
);
|
||||
_scheduleWidgetPublish(data, const Duration(seconds: 1));
|
||||
}
|
||||
});
|
||||
// Initial publish in case hydrated storage already has data. The widget
|
||||
@@ -194,19 +201,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
// frames are done (a fresh bloc emit in the meantime supersedes it).
|
||||
final initialData = timetable.state.data;
|
||||
if (initialData is TimetableState) {
|
||||
final epoch = SessionManager().sessionEpoch;
|
||||
Debouncer.debounce(
|
||||
'widgetPublish',
|
||||
const Duration(seconds: 3),
|
||||
() => unawaited(
|
||||
WidgetPublisher.publishFromBlocState(
|
||||
initialData,
|
||||
settings: settingsCubit.val(),
|
||||
isTeacher: capabilitiesCubit.isTeacher,
|
||||
epoch: epoch,
|
||||
),
|
||||
),
|
||||
);
|
||||
_scheduleWidgetPublish(initialData, const Duration(seconds: 3));
|
||||
}
|
||||
unawaited(_handlePendingWidgetNavigation());
|
||||
ShareIntentListener.pending.addListener(_handlePendingShare);
|
||||
|
||||
@@ -9,15 +9,13 @@ import '../api/marianumconnect/marianumconnect_endpoint.dart';
|
||||
import '../api/marianumconnect/queries/timetable_custom_events/timetable_custom_events_get.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_subjects/timetable_get_subjects_response.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_timegrid/timetable_get_timegrid_response.dart';
|
||||
import '../api/marianumconnect/queries/timetable_get_week/timetable_get_week.dart';
|
||||
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
|
||||
import '../session/session.dart';
|
||||
import '../extensions/date_time.dart';
|
||||
import '../session/session_manager.dart';
|
||||
import '../widget_data/widget_data_mapper.dart';
|
||||
import '../widget_data/widget_publisher.dart';
|
||||
@@ -81,16 +79,12 @@ class WidgetBackgroundTask {
|
||||
/// Throws on fetch failure so the worker path can signal a retry.
|
||||
static Future<void> runRefreshNow({bool force = false}) async {
|
||||
await WidgetSync.ensureInitialized();
|
||||
Session? session;
|
||||
try {
|
||||
// Bounded: a hanging keystore read must not stall the caller's budget
|
||||
// (FCM handler ~25s on iOS) forever.
|
||||
session = await SessionManager().waitForLoad().timeout(
|
||||
const Duration(seconds: 10),
|
||||
);
|
||||
} on TimeoutException {
|
||||
session = null;
|
||||
}
|
||||
// Bounded: a hanging keystore read must not stall the caller's budget
|
||||
// (FCM handler ~25s on iOS) forever.
|
||||
final session = await SessionManager().waitForLoad().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => null,
|
||||
);
|
||||
if (session == null) {
|
||||
// Deliberately does NOT flip the widget to logged-out: a failed or slow
|
||||
// keychain read (locked iOS device during the 06:00 silent push) is
|
||||
@@ -143,7 +137,6 @@ void _callbackDispatcher() {
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
await WidgetSync.ensureInitialized();
|
||||
// The background isolate doesn't go through main.dart's BlocBuilder, so we
|
||||
// re-apply the endpoint the foreground last persisted. Without this the
|
||||
// dio singleton would fall back to its hardcoded live default even when
|
||||
@@ -157,9 +150,7 @@ Future<void> _refresh() async {
|
||||
// Fetch window matches the week payload's window so the widget can roll
|
||||
// forward into next week's lessons without fresh data.
|
||||
final weekStart = WidgetDataMapper.startOfCalendarWeek(now);
|
||||
final weekEndExclusive = weekStart.add(
|
||||
const Duration(days: WidgetDataMapper.weekWindowDays),
|
||||
);
|
||||
final weekEndExclusive = weekStart.addDays(WidgetDataMapper.weekWindowDays);
|
||||
|
||||
// All six requests are independent — run them concurrently so the total
|
||||
// latency is the slowest request, not the sum (matters for the push path's
|
||||
@@ -167,14 +158,11 @@ Future<void> _refresh() async {
|
||||
// mapper rather than aborting the whole refresh.
|
||||
final timetableFuture = TimetableGetWeek().run(
|
||||
from: weekStart,
|
||||
until: weekEndExclusive.subtract(const Duration(days: 1)),
|
||||
until: weekEndExclusive.subtractDays(1),
|
||||
);
|
||||
final subjectsFuture = _runOrNull<TimetableGetSubjectsResponse>(
|
||||
() => TimetableGetSubjects().run(),
|
||||
);
|
||||
final roomsFuture = _runOrNull<TimetableGetRoomsResponse>(
|
||||
() => TimetableGetRooms().run(),
|
||||
);
|
||||
final holidaysFuture = _runOrNull<TimetableGetHolidaysResponse>(
|
||||
() => TimetableGetHolidays().run(),
|
||||
);
|
||||
@@ -186,33 +174,18 @@ Future<void> _refresh() async {
|
||||
);
|
||||
final timetable = await timetableFuture;
|
||||
final subjects = await subjectsFuture;
|
||||
final rooms = await roomsFuture;
|
||||
final holidays = await holidaysFuture;
|
||||
final timegrid = await timegridFuture;
|
||||
final customEvents = await customEventsFuture;
|
||||
|
||||
final lessons = timetable.entries;
|
||||
|
||||
final [connectDouble, isTeacher] = await Future.wait([
|
||||
WidgetSync.getConnectDoubleLessons(),
|
||||
WidgetSync.getIsTeacher(),
|
||||
]);
|
||||
final dayData = WidgetDataMapper.buildDayData(
|
||||
final (day: dayData, week: weekData) = WidgetDataMapper.buildSnapshot(
|
||||
now: now,
|
||||
lessons: lessons,
|
||||
lessons: timetable.entries,
|
||||
subjects: subjects,
|
||||
rooms: rooms,
|
||||
holidays: holidays,
|
||||
timegrid: timegrid,
|
||||
customEvents: customEvents,
|
||||
connectDoubleLessons: connectDouble,
|
||||
showClassInsteadOfTeacher: isTeacher,
|
||||
);
|
||||
final weekData = WidgetDataMapper.buildWeekData(
|
||||
now: now,
|
||||
lessons: lessons,
|
||||
subjects: subjects,
|
||||
rooms: rooms,
|
||||
holidays: holidays,
|
||||
timegrid: timegrid,
|
||||
customEvents: customEvents,
|
||||
@@ -220,10 +193,7 @@ Future<void> _refresh() async {
|
||||
showClassInsteadOfTeacher: isTeacher,
|
||||
);
|
||||
|
||||
await WidgetSync.writeDayData(dayData);
|
||||
await WidgetSync.writeWeekData(weekData);
|
||||
await WidgetSync.setLoggedIn(true);
|
||||
await WidgetSync.triggerUpdate();
|
||||
await WidgetSync.publishSnapshot(day: dayData, week: weekData);
|
||||
log(
|
||||
'[widget-bg] refreshed: day=${dayData.lessons.length} '
|
||||
'week=${weekData.lessons.length}',
|
||||
|
||||
@@ -72,9 +72,7 @@ extension DateTimeFormatting on DateTime {
|
||||
|
||||
String formatDateRelativeShort({DateTime? now}) {
|
||||
final reference = now ?? DateTime.now();
|
||||
final today = DateTime(reference.year, reference.month, reference.day);
|
||||
final self = DateTime(year, month, day);
|
||||
final diff = today.difference(self).inDays;
|
||||
final diff = reference.dateOnly.difference(dateOnly).inDays;
|
||||
|
||||
if (diff == 0) return 'Heute';
|
||||
if (diff == 1) return 'Gestern';
|
||||
|
||||
+7
-1
@@ -11,11 +11,13 @@ import 'package:flutter/scheduler.dart' show timeDilation;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:loader_overlay/loader_overlay.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
|
||||
import 'api/demo/demo_http_client.dart';
|
||||
import 'api/marianumcloud/webdav/queries/list_files/list_files_cache.dart';
|
||||
import 'api/marianumconnect/auth/session_validator.dart';
|
||||
import 'api/marianumconnect/marianumconnect_endpoint.dart';
|
||||
@@ -117,7 +119,11 @@ void _installErrorHandlers() {
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
// Everything runs inside the zone so every package:http client created by the
|
||||
// app (Talk, avatars, image caches) goes through DemoHttpClient.
|
||||
Future<void> main() => http.runWithClient(_main, DemoHttpClient.new);
|
||||
|
||||
Future<void> _main() async {
|
||||
log('MarianumMobile started');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
SessionManager().markUiEngine();
|
||||
|
||||
@@ -5,6 +5,9 @@ class Endpoint {
|
||||
Endpoint({required this.domain, this.path = ''});
|
||||
|
||||
String full() => domain + path;
|
||||
|
||||
/// `https://` + [full], without a trailing slash.
|
||||
String origin() => 'https://${full()}';
|
||||
}
|
||||
|
||||
class EndpointData {
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../api/marianumcloud/talk/share_files_to_chat.dart';
|
||||
@@ -53,7 +52,7 @@ abstract final class SessionWipe {
|
||||
/// unregister it).
|
||||
static Future<void> accountLeft() async {
|
||||
await _stepAsync('download files', () async {
|
||||
final dir = Directory('${(await getTemporaryDirectory()).path}/downloads');
|
||||
final dir = await DownloadManager.downloadsDirectory();
|
||||
if (dir.existsSync()) await dir.delete(recursive: true);
|
||||
});
|
||||
await _stepAsync(
|
||||
|
||||
@@ -15,7 +15,7 @@ import '../widget/info_dialog.dart';
|
||||
import 'notification_tasks.dart';
|
||||
|
||||
/// Bridges FCM lifecycle callbacks to the push pipeline. Background messages are
|
||||
/// handled directly by [PushMessageHandler.onBackgroundMessage]; this class
|
||||
/// handled directly by `pushOnBackgroundMessage`; this class
|
||||
/// covers the foreground and app-opened paths where a [BuildContext] is
|
||||
/// available.
|
||||
class NotificationController {
|
||||
|
||||
@@ -27,17 +27,13 @@ class NotificationTasks {
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-chat tag scheme — canonical definition lives beside the stacked
|
||||
/// notification id in chat_thread_store.dart.
|
||||
static String chatTag(String chatToken) => chatNotificationTag(chatToken);
|
||||
|
||||
/// Removes tray notifications belonging to [chatToken]. Eraser handles
|
||||
/// iOS (where the plugin's `getActiveNotifications` returns null ids
|
||||
/// for FCM posts and can't cancel them); the local-notifications sweep
|
||||
/// handles Android and acts as a fallback while Eraser's native side
|
||||
/// isn't built in yet.
|
||||
static Future<void> clearNotificationsForChat(String chatToken) async {
|
||||
final tag = chatTag(chatToken);
|
||||
final tag = chatNotificationTag(chatToken);
|
||||
try {
|
||||
await Eraser.clearAppNotificationsByTag(tag);
|
||||
} on MissingPluginException {
|
||||
|
||||
@@ -6,13 +6,20 @@ import 'package:localstore/localstore.dart';
|
||||
/// notification, small enough to stay cheap in the background isolate.
|
||||
const int kChatThreadCap = 15;
|
||||
|
||||
/// Canonical per-chat notification tag (kept in sync with
|
||||
/// `NotificationTasks.chatTag`, which delegates here).
|
||||
/// Canonical per-chat notification tag.
|
||||
String chatNotificationTag(String chatToken) => 'talk_$chatToken';
|
||||
|
||||
/// Deterministic non-negative 31-bit notification id per chat, so every
|
||||
/// message of the same conversation updates ONE stacked notification instead
|
||||
/// of adding a new card per push.
|
||||
final _fileSafeToken = RegExp(r'^[A-Za-z0-9_-]+$');
|
||||
|
||||
/// File-/doc-safe name for a chat token: normally URL-safe and used verbatim,
|
||||
/// anything else is base64url-encoded so exotic ids can't escape a directory.
|
||||
String fileSafeToken(String chatToken) => _fileSafeToken.hasMatch(chatToken)
|
||||
? chatToken
|
||||
: base64Url.encode(utf8.encode(chatToken)).replaceAll('=', '');
|
||||
|
||||
int stableChatNotificationId(String chatToken) {
|
||||
var hash = 0;
|
||||
for (final unit in chatToken.codeUnits) {
|
||||
@@ -128,12 +135,8 @@ class ChatThreadStore {
|
||||
|
||||
ChatThreadStore({Localstore? db}) : _db = db ?? Localstore.instance;
|
||||
|
||||
/// Localstore doc ids become file names — tokens are normally URL-safe and
|
||||
/// used verbatim, anything else is base64url-encoded.
|
||||
static String docIdForToken(String chatToken) =>
|
||||
RegExp(r'^[A-Za-z0-9_-]+$').hasMatch(chatToken)
|
||||
? chatToken
|
||||
: base64Url.encode(utf8.encode(chatToken)).replaceAll('=', '');
|
||||
/// Localstore doc ids become file names.
|
||||
static String docIdForToken(String chatToken) => fileSafeToken(chatToken);
|
||||
|
||||
Future<List<ThreadMessage>> messages(String chatToken) async {
|
||||
final data = await _doc(chatToken).get();
|
||||
@@ -145,16 +148,6 @@ class ChatThreadStore {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Appends [message] and returns the updated (capped) history.
|
||||
Future<List<ThreadMessage>> append(
|
||||
String chatToken,
|
||||
ThreadMessage message,
|
||||
) async {
|
||||
final updated = appendThreadMessage(await messages(chatToken), message);
|
||||
await _write(chatToken, updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// Appends [message], but restarts the thread first when the chat's previous
|
||||
/// notification is no longer active ([isActive] == false — dismissed/read).
|
||||
/// See [threadAfterIncoming] for the tri-state semantics.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:ui';
|
||||
|
||||
@@ -13,6 +12,7 @@ import '../session/session_manager.dart';
|
||||
import 'chat_thread_store.dart';
|
||||
import 'nid_store.dart';
|
||||
import 'push_renderer.dart';
|
||||
import 'push_target.dart';
|
||||
|
||||
/// Notification action identifiers shared between the renderer (which attaches
|
||||
/// the actions) and the response handlers (which dispatch them).
|
||||
@@ -93,8 +93,7 @@ class PushActions {
|
||||
// the mark-read action does. Best effort: a failure here must not
|
||||
// fail the already-delivered reply.
|
||||
await markRead(chatToken);
|
||||
}
|
||||
if (!result.ok) {
|
||||
} else {
|
||||
// Never swallow the typed message: surface the failure (and the
|
||||
// undelivered text) as its own notification. The nid mapping stays
|
||||
// alive — the thread notification is still in the tray.
|
||||
@@ -287,14 +286,6 @@ class PushActions {
|
||||
return raw == null ? null : int.tryParse(raw);
|
||||
}
|
||||
|
||||
static String? _payloadField(String? payload, String key) {
|
||||
if (payload == null || payload.isEmpty) return null;
|
||||
try {
|
||||
final map = jsonDecode(payload) as Map<String, dynamic>;
|
||||
final value = map[key];
|
||||
return value?.toString();
|
||||
} on Object {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static String? _payloadField(String? payload, String key) =>
|
||||
decodeNotificationPayload(payload)?[key]?.toString();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../api/marianumcloud/nextcloud_ocs.dart';
|
||||
import 'chat_thread_store.dart';
|
||||
|
||||
/// Result of an avatar lookup at render time: [icon] when bytes are available
|
||||
/// within the fetch budget, otherwise [late] carries the still-running fetch
|
||||
@@ -102,13 +103,7 @@ class PushAvatarStore {
|
||||
},
|
||||
);
|
||||
|
||||
/// File-safe cache name for a chat token. Tokens are normally URL-safe
|
||||
/// already and used verbatim; anything else is base64url-encoded so exotic
|
||||
/// ids can never escape the cache directory.
|
||||
static String fileNameForToken(String token) =>
|
||||
RegExp(r'^[A-Za-z0-9_-]+$').hasMatch(token)
|
||||
? token
|
||||
: base64Url.encode(utf8.encode(token)).replaceAll('=', '');
|
||||
static String fileNameForToken(String token) => fileSafeToken(token);
|
||||
|
||||
/// Whether a cache file written at [modified] is still fresh at [now].
|
||||
static bool isFresh(DateTime modified, DateTime now) =>
|
||||
|
||||
@@ -101,7 +101,6 @@ class PushMessageHandler {
|
||||
case PushKind.connect:
|
||||
await _handleConnect(
|
||||
message,
|
||||
foreground: foreground,
|
||||
notificationsEnabled: notificationsEnabled,
|
||||
);
|
||||
break;
|
||||
@@ -139,7 +138,6 @@ class PushMessageHandler {
|
||||
|
||||
Future<void> _handleConnect(
|
||||
RemoteMessage message, {
|
||||
required bool foreground,
|
||||
required bool notificationsEnabled,
|
||||
}) async {
|
||||
// Connect pushes carry no silent side effects, so nothing to do when the
|
||||
|
||||
@@ -60,7 +60,7 @@ class PushRegistration {
|
||||
|
||||
/// Nextcloud origin the registration targets (full origin, no trailing
|
||||
/// slash) — persisted alongside the registration to detect endpoint changes.
|
||||
String get currentNcBaseUrl => 'https://${EndpointData().nextcloud().full()}';
|
||||
String get currentNcBaseUrl => EndpointData().nextcloud().origin();
|
||||
|
||||
NextcloudCredentials? get _nextcloudOrNull =>
|
||||
SessionManager().current?.nextcloud;
|
||||
@@ -258,10 +258,9 @@ class PushRegistration {
|
||||
/// Flutter engine. Best-effort — a failure here must not abort registration.
|
||||
Future<void> _persistNativeAuthContext() async {
|
||||
try {
|
||||
final endpoint = EndpointData().nextcloud();
|
||||
await _store.saveNativeAuthContext(
|
||||
username: SessionManager().requireNextcloud().username,
|
||||
baseUrl: 'https://${endpoint.full()}',
|
||||
baseUrl: currentNcBaseUrl,
|
||||
);
|
||||
} on Object catch (e) {
|
||||
log('Push: could not persist native auth context: $e');
|
||||
|
||||
+26
-23
@@ -203,16 +203,10 @@ class PushRenderer {
|
||||
messages: styleMessages,
|
||||
);
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
talkChannelId,
|
||||
talkChannelName,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
category: AndroidNotificationCategory.message,
|
||||
color: _accentColor,
|
||||
final androidDetails = _talkAndroidDetails(
|
||||
tag: tag,
|
||||
style: messagingStyle,
|
||||
silent: !alert,
|
||||
styleInformation: messagingStyle,
|
||||
actions: _talkActions,
|
||||
);
|
||||
|
||||
@@ -247,17 +241,11 @@ class PushRenderer {
|
||||
title: senderName,
|
||||
body: messageText,
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
talkChannelId,
|
||||
talkChannelName,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
category: AndroidNotificationCategory.message,
|
||||
color: _accentColor,
|
||||
android: _talkAndroidDetails(
|
||||
tag: tag,
|
||||
// Single sender ⇒ no conversationTitle (1:1 convention): the sender
|
||||
// Person is the header, a title would repeat the name per line.
|
||||
styleInformation: MessagingStyleInformation(
|
||||
style: MessagingStyleInformation(
|
||||
const Person(key: 'self', name: 'Ich'),
|
||||
groupConversation: false,
|
||||
messages: [
|
||||
@@ -271,6 +259,25 @@ class PushRenderer {
|
||||
);
|
||||
}
|
||||
|
||||
static AndroidNotificationDetails _talkAndroidDetails({
|
||||
required String tag,
|
||||
required StyleInformation style,
|
||||
AndroidNotificationCategory? category = AndroidNotificationCategory.message,
|
||||
bool silent = false,
|
||||
List<AndroidNotificationAction>? actions,
|
||||
}) => AndroidNotificationDetails(
|
||||
talkChannelId,
|
||||
talkChannelName,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
category: category,
|
||||
color: _accentColor,
|
||||
tag: tag,
|
||||
silent: silent,
|
||||
styleInformation: style,
|
||||
actions: actions,
|
||||
);
|
||||
|
||||
// Both actions keep cancelNotification: true (the default): the plugin's
|
||||
// Java receiver then removes the card NATIVELY the moment the action fires.
|
||||
// The reply action must not rely on our Dart-side cancel instead — MIUI/
|
||||
@@ -306,14 +313,10 @@ class PushRenderer {
|
||||
title: title,
|
||||
body: body,
|
||||
notificationDetails: NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
talkChannelId,
|
||||
talkChannelName,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
color: _accentColor,
|
||||
android: _talkAndroidDetails(
|
||||
tag: 'talk_error_$chatToken',
|
||||
styleInformation: BigTextStyleInformation(body),
|
||||
style: BigTextStyleInformation(body),
|
||||
category: null,
|
||||
),
|
||||
iOS: const DarwinNotificationDetails(),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -25,7 +24,7 @@ class PushTapRouter {
|
||||
PushActions.handleBackgroundResponse(response);
|
||||
return;
|
||||
}
|
||||
final map = _payloadMap(response.payload);
|
||||
final map = decodeNotificationPayload(response.payload);
|
||||
if (map == null) return;
|
||||
final target = resolvePushTarget(map);
|
||||
if (target != null) pendingTarget.value = target;
|
||||
@@ -50,13 +49,4 @@ class PushTapRouter {
|
||||
log('Reading the notification launch details failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _payloadMap(String? payload) {
|
||||
if (payload == null || payload.isEmpty) return null;
|
||||
try {
|
||||
return jsonDecode(payload) as Map<String, dynamic>;
|
||||
} on Object {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user