21 Commits

Author SHA1 Message Date
MineTec 3b1b0d0c19 fixed lesson merging mutation, improved overlap detection, and implemented priority-based lane assignment with tablet support 2026-05-07 13:27:40 +02:00
MineTec c32e64fe74 improved yOfDateTime precision and period-based calculation in workweek calendar 2026-05-07 09:51:13 +02:00
MineTec 710e88d744 refactored chat data fetching to support separate cache and network callbacks 2026-05-07 09:46:30 +02:00
MineTec 517e515ac1 centered login view and updated layout constraints 2026-05-07 09:11:09 +02:00
MineTec e8f0c4383c added camera support and enabled gallery selection on ios 2026-05-06 23:09:44 +02:00
MineTec b8cac73e74 updated timetable UI with event status and enhanced appointment tile rendering 2026-05-06 22:53:24 +02:00
MineTec 95ef29fb09 implemented dynamic module settings and configurable bottom bar, added all-day event support to timetable, and overhauled marianum dates UI with month grouping and search 2026-05-06 22:37:41 +02:00
MineTec 86d12884fc custom login implementation, period-based timetable layout with overlap handling, enhanced error dialogs, and unified bottom sheets 2026-05-06 20:42:09 +02:00
MineTec 50d2941e52 refactored lesson details, centralized logout logic, and added resume re-fetch 2026-05-06 16:27:45 +02:00
MineTec 71506aab2d Merge remote-tracking branch 'origin/develop-refactor' into develop-refactor 2026-05-06 11:59:17 +02:00
MineTec 4e1272aba9 claude refactorings, flutter best practices, platform dependent changes, general cleanup 2026-05-06 11:59:01 +02:00
MineTec 72ebe6f7e7 claude refactorings, flutter best practices, platform dependent changes, general cleanup 2026-05-06 11:58:50 +02:00
MineTec 4b1d4379a0 loading state and error handling refactor 2026-05-06 10:11:45 +02:00
MineTec 2c376afd91 removed stray character in settings.gradle 2026-05-05 22:38:07 +02:00
MineTec 54ba04a7bd wait for account data population and set initial AccountBloc status 2026-05-05 22:08:10 +02:00
MineTec 9b5a70b285 api and storage restructure 2026-05-05 22:00:07 +02:00
MineTec 4f796dac2e folder restructuring 2026-05-05 21:44:23 +02:00
MineTec db9c3386f1 better loading indicators for timetables, talk and files 2026-05-05 21:07:48 +02:00
MineTec bee5c02a4f marianum appointments 2026-05-05 16:05:07 +02:00
MineTec e8faa77e70 refactored timetable 2026-05-05 13:49:45 +02:00
MineTec 551c1bf1fa claude refactor 2026-05-04 13:54:39 +02:00
438 changed files with 14260 additions and 5665 deletions
+73 -29
View File
@@ -1,44 +1,88 @@
# This file configures the analyzer, which statically analyzes Dart code to # Static analysis configuration for the Flutter project.
# check for errors, warnings, and lints. # https://dart.dev/guides/language/analysis-options
# #
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled # Base ruleset: flutter_lints (recommended Flutter defaults).
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # Additional lints below catch real bugs and enforce consistent style.
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer: analyzer:
language:
strict-casts: true
strict-raw-types: true
errors: errors:
invalid_annotation_target: ignore invalid_annotation_target: ignore
todo: ignore
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "lib/firebase_options.dart"
- "build/**"
linter: linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule # === Project conventions ===
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
file_names: false
prefer_relative_imports: true prefer_relative_imports: true
unnecessary_lambdas: true
prefer_single_quotes: true prefer_single_quotes: true
prefer_if_elements_to_conditional_expressions: true
prefer_expression_function_bodies: true
omit_local_variable_types: true
eol_at_end_of_file: true eol_at_end_of_file: true
cast_nullable_to_non_nullable: true omit_local_variable_types: true
avoid_void_async: true
avoid_multiple_declarations_per_line: true avoid_multiple_declarations_per_line: true
# Additional information about this file can be found at # === Bug catchers ===
# https://dart.dev/guides/language/analysis-options always_declare_return_types: true
avoid_empty_else: true
avoid_slow_async_io: true
avoid_type_to_string: true
avoid_void_async: true
await_only_futures: true
cancel_subscriptions: true
cast_nullable_to_non_nullable: true
close_sinks: true
empty_catches: true
hash_and_equals: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
test_types_in_equals: true
throw_in_finally: true
unawaited_futures: true
unnecessary_statements: true
unrelated_type_equality_checks: true
use_build_context_synchronously: true
valid_regexps: true
# === Flutter widget hygiene ===
avoid_unnecessary_containers: true
sized_box_for_whitespace: true
sort_child_properties_last: true
use_colored_box: true
use_decorated_box: true
use_full_hex_values_for_flutter_colors: true
use_key_in_widget_constructors: true
# === Code clarity ===
directives_ordering: true
library_prefixes: true
no_leading_underscores_for_local_identifiers: true
prefer_conditional_assignment: true
prefer_if_elements_to_conditional_expressions: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_null_aware_operators: true
prefer_spread_collections: true
prefer_void_to_null: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_lambdas: true
unnecessary_null_aware_assignments: true
unnecessary_null_checks: true
unnecessary_parenthesis: true
unnecessary_string_interpolations: true
use_super_parameters: true
# === File naming ===
file_names: true
+1 -1
View File
@@ -25,7 +25,7 @@ if (flutterVersionName == null) {
android { android {
namespace "eu.mhsl.marianum.mobile.client" namespace "eu.mhsl.marianum.mobile.client"
compileSdk flutter.compileSdkVersion compileSdk flutter.compileSdkVersion
ndkVersion "27.0.12077973" ndkVersion "28.2.13676358"
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+1 -1
View File
@@ -54,7 +54,7 @@ flutter_native_splash:
# 640 pixels in diameter. # 640 pixels in diameter.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter. # 768 pixels in diameter.
image: assets/logo/icon-android12.png image: assets/logo/icon/icon-android12.png
# Splash screen background color. # Splash screen background color.
color: "#993333" color: "#993333"
+2
View File
@@ -26,6 +26,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <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> <key>NSPhotoLibraryUsageDescription</key>
<string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string> <string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
@@ -1,4 +1,4 @@
class ApiError { class ApiError implements Exception {
String message; String message;
ApiError(this.message); ApiError(this.message);
+16
View File
@@ -0,0 +1,16 @@
abstract class AppException implements Exception {
final String userMessage;
final String? technicalDetails;
final bool allowRetry;
const AppException({
required this.userMessage,
this.technicalDetails,
this.allowRetry = true,
});
@override
String toString() => technicalDetails == null
? '$runtimeType: $userMessage'
: '$runtimeType: $userMessage ($technicalDetails)';
}
+23
View File
@@ -0,0 +1,23 @@
import 'app_exception.dart';
class AuthException extends AppException {
final int statusCode;
const AuthException({
required this.statusCode,
required super.userMessage,
super.technicalDetails,
}) : super(allowRetry: false);
factory AuthException.unauthorized({String? technicalDetails}) => AuthException(
statusCode: 401,
userMessage: 'Bitte melde dich erneut an, um fortzufahren.',
technicalDetails: technicalDetails,
);
factory AuthException.forbidden({String? technicalDetails}) => AuthException(
statusCode: 403,
userMessage: 'Du hast keine Berechtigung für diese Aktion.',
technicalDetails: technicalDetails,
);
}
+64
View File
@@ -0,0 +1,64 @@
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../api_error.dart';
import '../marianumcloud/talk/talk_error.dart';
import '../webuntis/webuntis_error.dart';
import 'app_exception.dart';
import 'network_exception.dart';
import 'parse_exception.dart';
import 'talk_exception.dart';
import 'webuntis_exception.dart';
const String _defaultFallback = 'Etwas ist schiefgelaufen. Bitte versuche es erneut.';
String errorToUserMessage(Object? error, {String fallback = _defaultFallback}) {
if (error == null) return fallback;
if (error is AppException) return error.userMessage;
if (error is TalkError) return TalkException(error).userMessage;
if (error is WebuntisError) return WebuntisException(error).userMessage;
if (error is SocketException) {
return const NetworkException().userMessage;
}
if (error is TimeoutException) {
return NetworkException.timeout().userMessage;
}
if (error is http.ClientException) {
return const NetworkException().userMessage;
}
if (error is HandshakeException) {
return 'Sichere Verbindung konnte nicht hergestellt werden.';
}
if (error is FormatException) {
return const ParseException().userMessage;
}
if (error is ApiError) {
return _stripDioPrefix(error.message);
}
return fallback;
}
String? errorToTechnicalDetails(Object? error) {
if (error == null) return null;
if (error is AppException) return error.technicalDetails ?? error.toString();
if (error is TalkError) return TalkException(error).technicalDetails;
if (error is WebuntisError) return WebuntisException(error).technicalDetails;
return error.toString();
}
bool errorAllowsRetry(Object? error) {
if (error == null) return true;
if (error is AppException) return error.allowRetry;
return true;
}
String _stripDioPrefix(String raw) {
// ApiError messages embed full request URIs; only surface the first line.
final firstLine = raw.split('\n').first.trim();
return firstLine.isEmpty ? _defaultFallback : firstLine;
}
+13
View File
@@ -0,0 +1,13 @@
import 'app_exception.dart';
class NetworkException extends AppException {
const NetworkException({
super.userMessage = 'Keine Internetverbindung. Bitte prüfe dein Netzwerk und versuche es erneut.',
super.technicalDetails,
}) : super(allowRetry: true);
factory NetworkException.timeout({String? technicalDetails}) => NetworkException(
userMessage: 'Der Server hat zu lange gebraucht. Bitte versuche es erneut.',
technicalDetails: technicalDetails,
);
}
+8
View File
@@ -0,0 +1,8 @@
import 'app_exception.dart';
class NotFoundException extends AppException {
const NotFoundException({
super.userMessage = 'Der angeforderte Eintrag wurde nicht gefunden.',
super.technicalDetails,
}) : super(allowRetry: false);
}
+8
View File
@@ -0,0 +1,8 @@
import 'app_exception.dart';
class ParseException extends AppException {
const ParseException({
super.userMessage = 'Die Antwort des Servers konnte nicht gelesen werden.',
super.technicalDetails,
}) : super(allowRetry: true);
}
+14
View File
@@ -0,0 +1,14 @@
import 'app_exception.dart';
class ServerException extends AppException {
final int statusCode;
ServerException({
required this.statusCode,
String? userMessage,
super.technicalDetails,
}) : super(
userMessage: userMessage ?? 'Der Server hat gerade Probleme (Status $statusCode). Bitte später erneut versuchen.',
allowRetry: true,
);
}
+33
View File
@@ -0,0 +1,33 @@
import '../marianumcloud/talk/talk_error.dart';
import 'app_exception.dart';
class TalkException extends AppException {
final TalkError source;
TalkException(this.source)
: super(
userMessage: _mapMessage(source),
technicalDetails: 'Talk ${source.status} (${source.code}): ${source.message}',
allowRetry: source.code >= 500,
);
static String _mapMessage(TalkError e) {
switch (e.code) {
case 401:
return 'Bitte melde dich erneut an, um auf Talk zuzugreifen.';
case 403:
return 'Du hast keine Berechtigung für diese Talk-Aktion.';
case 404:
return 'Dieser Chat existiert nicht oder wurde entfernt.';
case 412:
return 'Diese Aktion ist im aktuellen Chat-Zustand nicht erlaubt.';
case 429:
return 'Zu viele Anfragen. Bitte kurz warten und erneut versuchen.';
default:
if (e.code >= 500) {
return 'Talk-Server hat gerade Probleme (${e.code}).';
}
return e.message.isNotEmpty ? e.message : 'Talk meldet einen Fehler (${e.code}).';
}
}
}
+31
View File
@@ -0,0 +1,31 @@
import '../webuntis/webuntis_error.dart';
import 'app_exception.dart';
class WebuntisException extends AppException {
final WebuntisError source;
WebuntisException(this.source)
: super(
userMessage: _mapMessage(source),
technicalDetails: 'WebUntis (${source.code}): ${source.message}',
allowRetry: true,
);
static String _mapMessage(WebuntisError e) {
switch (e.code) {
case -8504:
case -8502:
return 'WebUntis-Anmeldung abgelaufen. Bitte erneut anmelden.';
case -8520:
return 'Bitte melde dich erneut an.';
case -7004:
return 'Für diesen Zeitraum sind keine Stundenplandaten verfügbar.';
case -32601:
return 'WebUntis kennt diese Anfrage nicht. Bitte App aktualisieren.';
default:
return e.message.isNotEmpty
? 'WebUntis: ${e.message}'
: 'WebUntis konnte die Anfrage nicht bearbeiten (Code ${e.code}).';
}
}
}
-26
View File
@@ -1,26 +0,0 @@
import 'dart:convert';
import '../requestCache.dart';
import 'getHolidays.dart';
import 'getHolidaysResponse.dart';
class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
GetHolidaysCache({onUpdate, renew}) : super(RequestCache.cacheDay, onUpdate, renew: renew) {
start('state-holidays');
}
@override
GetHolidaysResponse onLocalData(String json) {
List<dynamic> parsedListJson = jsonDecode(json)['data'];
return GetHolidaysResponse(
List<GetHolidaysResponseObject>.from(
parsedListJson.map<GetHolidaysResponseObject>(
(i) => GetHolidaysResponseObject.fromJson(i as Map<String, dynamic>)
)
)
);
}
@override
Future<GetHolidaysResponse> onLoad() => GetHolidays().query();
}
@@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'getHolidaysResponse.dart'; import 'get_holidays_response.dart';
class GetHolidays { class GetHolidays {
Future<GetHolidaysResponse> query() async { Future<GetHolidaysResponse> query() async {
+18
View File
@@ -0,0 +1,18 @@
import '../request_cache.dart';
import 'get_holidays.dart';
import 'get_holidays_response.dart';
class GetHolidaysCache extends SimpleCache<GetHolidaysResponse> {
GetHolidaysCache({super.onUpdate, super.renew})
: super(
cacheTime: RequestCache.cacheDay,
loader: () => GetHolidays().query(),
fromJson: (json) => GetHolidaysResponse(
(json['data'] as List)
.map((i) => GetHolidaysResponseObject.fromJson(i as Map<String, dynamic>))
.toList(),
),
) {
start('state-holidays');
}
}
@@ -1,9 +1,9 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../apiResponse.dart'; import '../api_response.dart';
part 'getHolidaysResponse.g.dart'; part 'get_holidays_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetHolidaysResponse extends ApiResponse { class GetHolidaysResponse extends ApiResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getHolidaysResponse.dart'; part of 'get_holidays_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,32 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../../../model/accountData.dart';
import '../../../model/endpointData.dart';
import 'autocompleteResponse.dart';
class AutocompleteApi {
Future<AutocompleteResponse> find(String query) async {
var getParameters = <String, dynamic>{
'search': query,
'itemType': ' ',
'itemId': ' ',
'shareTypes[]': ['0'],
'limit': '10',
};
var headers = <String, String>{};
headers.putIfAbsent('Accept', () => 'application/json');
headers.putIfAbsent('OCS-APIRequest', () => 'true');
var endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/core/autocomplete/get', getParameters);
var response = await http.get(endpoint, headers: headers);
if(response.statusCode != HttpStatus.ok) throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
var result = response.body;
return AutocompleteResponse.fromJson(jsonDecode(result)['ocs']);
}
}
@@ -0,0 +1,28 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../nextcloud_ocs.dart';
import 'autocomplete_response.dart';
class AutocompleteApi {
Future<AutocompleteResponse> find(String query) async {
final endpoint = NextcloudOcs.uri(
'core/autocomplete/get',
queryParameters: {
'search': query,
'itemType': ' ',
'itemId': ' ',
'shareTypes[]': ['0'],
'limit': '10',
},
);
final response = await http.get(endpoint, headers: NextcloudOcs.headers());
if (response.statusCode != HttpStatus.ok) {
throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
}
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
return AutocompleteResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
}
@@ -1,6 +1,6 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'autocompleteResponse.g.dart'; part 'autocomplete_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class AutocompleteResponse { class AutocompleteResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'autocompleteResponse.dart'; part of 'autocomplete_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,22 +0,0 @@
import 'dart:io';
import 'package:http/http.dart' as http;
import '../../../model/accountData.dart';
import '../../../model/endpointData.dart';
import 'fileSharingApiParams.dart';
class FileSharingApi {
Future<void> share(FileSharingApiParams query) async {
var headers = <String, String>{};
headers.putIfAbsent('Accept', () => 'application/json');
headers.putIfAbsent('OCS-APIRequest', () => 'true');
var endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/apps/files_sharing/api/v1/shares', query.toJson().map((key, value) => MapEntry(key, value.toString())));
var response = await http.post(endpoint, headers: headers);
if(response.statusCode != HttpStatus.ok) {
throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
}
}
}
@@ -0,0 +1,19 @@
import 'dart:io';
import 'package:http/http.dart' as http;
import '../nextcloud_ocs.dart';
import 'file_sharing_api_params.dart';
class FileSharingApi {
Future<void> share(FileSharingApiParams query) async {
final endpoint = NextcloudOcs.uri(
'apps/files_sharing/api/v1/shares',
queryParameters: query.toJson(),
);
final response = await http.post(endpoint, headers: NextcloudOcs.headers());
if (response.statusCode != HttpStatus.ok) {
throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
}
}
}
@@ -1,6 +1,6 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'fileSharingApiParams.g.dart'; part 'file_sharing_api_params.g.dart';
@JsonSerializable() @JsonSerializable()
class FileSharingApiParams { class FileSharingApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fileSharingApiParams.dart'; part of 'file_sharing_api_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
+33
View File
@@ -0,0 +1,33 @@
import '../../model/account_data.dart';
import '../../model/endpoint_data.dart';
/// Shared helpers for Nextcloud OCS v2 endpoints.
///
/// Three call sites previously duplicated the same header dictionary and the
/// same URI scaffolding (TalkApi, AutocompleteApi, FileSharingApi). Anything
/// that talks to `https://<domain>/<base>/ocs/v2.php/...` should go through
/// these two helpers so additions like a new header or a different auth
/// scheme only need to change here.
class NextcloudOcs {
NextcloudOcs._();
/// The standard OCS request header set: JSON accept, OCS API marker,
/// HTTP Basic auth from the active [AccountData].
static Map<String, String> headers() => {
'Accept': 'application/json',
'OCS-APIRequest': 'true',
'Authorization': AccountData().getBasicAuthHeader(),
};
/// Builds an OCS URI by appending [pathSuffix] under `/ocs/v2.php/` of
/// the configured Nextcloud endpoint. Query parameters are converted to
/// strings (Uri rejects non-string values).
static Uri uri(String pathSuffix, {Map<String, dynamic>? queryParameters}) {
final endpoint = EndpointData().nextcloud();
return Uri.https(
endpoint.domain,
'${endpoint.path}/ocs/v2.php/$pathSuffix',
queryParameters?.map((key, value) => MapEntry(key, value.toString())),
);
}
}
@@ -0,0 +1,50 @@
import 'package:http/http.dart' as http;
import '../../../api_params.dart';
import '../../../api_response.dart';
import '../talk_api.dart';
/// Small POST/DELETE-only Talk endpoints that have no response payload.
/// Each class extends [TalkApi] with `assemble` returning `null`. They share
/// no state — they're collected here purely to avoid eight near-empty files.
class SetFavorite extends TalkApi {
final String chatToken;
final bool favoriteState;
SetFavorite(this.chatToken, this.favoriteState) : super('v4/room/$chatToken/favorite', null);
@override
ApiResponse? assemble(String raw) => null;
@override
Future<http.Response> request(Uri uri, ApiParams? body, Map<String, String>? headers) =>
favoriteState ? http.post(uri, headers: headers) : http.delete(uri, headers: headers);
}
class LeaveRoom extends TalkApi {
final String chatToken;
LeaveRoom(this.chatToken) : super('v4/room/$chatToken/participants/self', null);
@override
ApiResponse? assemble(String raw) => null;
@override
Future<http.Response> request(Uri uri, ApiParams? body, Map<String, String>? headers) =>
http.delete(uri, headers: headers);
}
class DeleteMessage extends TalkApi {
final String chatToken;
final int messageId;
DeleteMessage(this.chatToken, this.messageId) : super('v1/chat/$chatToken/$messageId', null);
@override
ApiResponse? assemble(String raw) => null;
@override
Future<http.Response> request(Uri uri, ApiParams? body, Map<String, String>? headers) =>
http.delete(uri, headers: headers);
}
@@ -1,28 +0,0 @@
import 'dart:convert';
import '../../../requestCache.dart';
import 'getChat.dart';
import 'getChatParams.dart';
import 'getChatResponse.dart';
class GetChatCache extends RequestCache<GetChatResponse> {
String chatToken;
GetChatCache({required onUpdate, required this.chatToken}) : super(RequestCache.cacheNothing, onUpdate) {
start('nc-chat-$chatToken');
}
@override
Future<GetChatResponse> onLoad() => GetChat(
chatToken,
GetChatParams(
lookIntoFuture: GetChatParamsSwitch.off,
setReadMarker: GetChatParamsSwitch.on,
limit: 200,
)
).run();
@override
GetChatResponse onLocalData(String json) => GetChatResponse.fromJson(jsonDecode(json));
}
@@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'getChatParams.dart'; import 'get_chat_params.dart';
import 'getChatResponse.dart'; import 'get_chat_response.dart';
class GetChat extends TalkApi<GetChatResponse> { class GetChat extends TalkApi<GetChatResponse> {
String chatToken; String chatToken;
@@ -14,7 +14,10 @@ class GetChat extends TalkApi<GetChatResponse> {
GetChat(this.chatToken, this.params) : super('v1/chat/$chatToken', null, getParameters: params.toJson()); GetChat(this.chatToken, this.params) : super('v1/chat/$chatToken', null, getParameters: params.toJson());
@override @override
assemble(String raw) => GetChatResponse.fromJson(jsonDecode(raw)['ocs']); GetChatResponse assemble(String raw) {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
return GetChatResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
@override @override
Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers); Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers);
@@ -0,0 +1,26 @@
import '../../../request_cache.dart';
import 'get_chat.dart';
import 'get_chat_params.dart';
import 'get_chat_response.dart';
class GetChatCache extends SimpleCache<GetChatResponse> {
GetChatCache({
super.onCacheData,
super.onNetworkData,
super.onError,
required String chatToken,
}) : super(
cacheTime: RequestCache.cacheNothing,
loader: () => GetChat(
chatToken,
GetChatParams(
lookIntoFuture: GetChatParamsSwitch.off,
setReadMarker: GetChatParamsSwitch.on,
limit: 200,
),
).run(),
fromJson: GetChatResponse.fromJson,
) {
start('nc-chat-$chatToken');
}
}
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'getChatParams.g.dart'; part 'get_chat_params.g.dart';
@JsonSerializable(explicitToJson: true, includeIfNull: false) @JsonSerializable(explicitToJson: true, includeIfNull: false)
class GetChatParams extends ApiParams { class GetChatParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getChatParams.dart'; part of 'get_chat_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,10 +1,10 @@
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart'; import '../../../api_response.dart';
import '../room/getRoomResponse.dart'; import '../room/get_room_response.dart';
part 'getChatResponse.g.dart'; part 'get_chat_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetChatResponse extends ApiResponse { class GetChatResponse extends ApiResponse {
@@ -86,11 +86,11 @@ class GetChatResponseObject {
} }
Map<String, RichObjectString>? _fromJson(json) { Map<String, RichObjectString>? _fromJson(dynamic json) {
if(json is Map<String, dynamic>) { if (json is Map<String, dynamic>) {
var data = <String, RichObjectString>{}; final data = <String, RichObjectString>{};
for (var element in json.keys) { for (final element in json.keys) {
data.putIfAbsent(element, () => RichObjectString.fromJson(json[element])); data.putIfAbsent(element, () => RichObjectString.fromJson(json[element] as Map<String, dynamic>));
} }
return data; return data;
} }
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getChatResponse.dart'; part of 'get_chat_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,5 +1,5 @@
import 'getChatResponse.dart'; import 'get_chat_response.dart';
class RichObjectStringProcessor { class RichObjectStringProcessor {
static String parseToString(String message, Map<String, RichObjectString>? data) { static String parseToString(String message, Map<String, RichObjectString>? data) {
@@ -2,15 +2,15 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'createRoomParams.dart'; import 'create_room_params.dart';
class CreateRoom extends TalkApi { class CreateRoom extends TalkApi {
CreateRoomParams params; CreateRoomParams params;
CreateRoom(this.params) : super('v4/room', params); CreateRoom(this.params) : super('v4/room', params);
@override @override
assemble(String raw) => null; Null assemble(String raw) => null;
@override @override
Future<Response>? request(Uri uri, Object? body, Map<String, String>? headers) { Future<Response>? request(Uri uri, Object? body, Map<String, String>? headers) {
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'createRoomParams.g.dart'; part 'create_room_params.g.dart';
@JsonSerializable() @JsonSerializable()
class CreateRoomParams extends ApiParams { class CreateRoomParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'createRoomParams.dart'; part of 'create_room_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,18 +0,0 @@
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../../../apiParams.dart';
import '../talkApi.dart';
class DeleteMessage extends TalkApi {
String chatToken;
int messageId;
DeleteMessage(this.chatToken, this.messageId) : super('v1/chat/$chatToken/$messageId', null);
@override
assemble(String raw) => null;
@override
Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) => http.delete(uri, headers: headers);
}
@@ -1,9 +1,9 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'deleteReactMessageParams.dart'; import 'delete_react_message_params.dart';
class DeleteReactMessage extends TalkApi { class DeleteReactMessage extends TalkApi {
String chatToken; String chatToken;
@@ -11,7 +11,7 @@ class DeleteReactMessage extends TalkApi {
DeleteReactMessage({required this.chatToken, required this.messageId, required DeleteReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params); DeleteReactMessage({required this.chatToken, required this.messageId, required DeleteReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params);
@override @override
assemble(String raw) => null; Null assemble(String raw) => null;
@override @override
Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) { Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) {
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'deleteReactMessageParams.g.dart'; part 'delete_react_message_params.g.dart';
@JsonSerializable() @JsonSerializable()
class DeleteReactMessageParams extends ApiParams { class DeleteReactMessageParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'deleteReactMessageParams.dart'; part of 'delete_react_message_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,22 +0,0 @@
import 'dart:convert';
import '../../../requestCache.dart';
import 'getParticipants.dart';
import 'getParticipantsResponse.dart';
class GetParticipantsCache extends RequestCache<GetParticipantsResponse> {
String chatToken;
GetParticipantsCache({required onUpdate, required this.chatToken}) : super(RequestCache.cacheNothing, onUpdate) {
start('nc-chat-participants-$chatToken');
}
@override
Future<GetParticipantsResponse> onLoad() => GetParticipants(
chatToken,
).run();
@override
GetParticipantsResponse onLocalData(String json) => GetParticipantsResponse.fromJson(jsonDecode(json));
}
@@ -2,15 +2,18 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../talkApi.dart'; import '../talk_api.dart';
import 'getParticipantsResponse.dart'; import 'get_participants_response.dart';
class GetParticipants extends TalkApi<GetParticipantsResponse> { class GetParticipants extends TalkApi<GetParticipantsResponse> {
String token; String token;
GetParticipants(this.token) : super('v4/room/$token/participants', null); GetParticipants(this.token) : super('v4/room/$token/participants', null);
@override @override
GetParticipantsResponse assemble(String raw) => GetParticipantsResponse.fromJson(jsonDecode(raw)['ocs']); GetParticipantsResponse assemble(String raw) {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
return GetParticipantsResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
@override @override
Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers); Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers);
@@ -0,0 +1,17 @@
import '../../../request_cache.dart';
import 'get_participants.dart';
import 'get_participants_response.dart';
class GetParticipantsCache extends SimpleCache<GetParticipantsResponse> {
GetParticipantsCache({
required void Function(GetParticipantsResponse) onUpdate,
required String chatToken,
}) : super(
cacheTime: RequestCache.cacheNothing,
loader: () => GetParticipants(chatToken).run(),
fromJson: GetParticipantsResponse.fromJson,
onUpdate: onUpdate,
) {
start('nc-chat-participants-$chatToken');
}
}
@@ -1,9 +1,9 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart'; import '../../../api_response.dart';
part 'getParticipantsResponse.g.dart'; part 'get_participants_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetParticipantsResponse extends ApiResponse { class GetParticipantsResponse extends ApiResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getParticipantsResponse.dart'; part of 'get_participants_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -2,8 +2,8 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../talkApi.dart'; import '../talk_api.dart';
import 'getPollStateResponse.dart'; import 'get_poll_state_response.dart';
class GetPollState extends TalkApi<GetPollStateResponse> { class GetPollState extends TalkApi<GetPollStateResponse> {
String token; String token;
@@ -11,7 +11,10 @@ class GetPollState extends TalkApi<GetPollStateResponse> {
GetPollState({required this.token, required this.pollId}) : super('v1/poll/$token/$pollId', null); GetPollState({required this.token, required this.pollId}) : super('v1/poll/$token/$pollId', null);
@override @override
GetPollStateResponse assemble(String raw) => GetPollStateResponse.fromJson(jsonDecode(raw)['ocs']); GetPollStateResponse assemble(String raw) {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
return GetPollStateResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
@override @override
Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers); Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers);
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart'; import '../../../api_response.dart';
part 'getPollStateResponse.g.dart'; part 'get_poll_state_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetPollStateResponse extends ApiResponse { class GetPollStateResponse extends ApiResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getPollStateResponse.dart'; part of 'get_poll_state_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -3,9 +3,9 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'getReactionsResponse.dart'; import 'get_reactions_response.dart';
class GetReactions extends TalkApi<GetReactionsResponse> { class GetReactions extends TalkApi<GetReactionsResponse> {
String chatToken; String chatToken;
@@ -13,7 +13,10 @@ class GetReactions extends TalkApi<GetReactionsResponse> {
GetReactions({required this.chatToken, required this.messageId}) : super('v1/reaction/$chatToken/$messageId', null); GetReactions({required this.chatToken, required this.messageId}) : super('v1/reaction/$chatToken/$messageId', null);
@override @override
assemble(String raw) => GetReactionsResponse.fromJson(jsonDecode(raw)['ocs']); GetReactionsResponse assemble(String raw) {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
return GetReactionsResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
@override @override
Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) => http.get(uri, headers: headers); Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) => http.get(uri, headers: headers);
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart'; import '../../../api_response.dart';
part 'getReactionsResponse.g.dart'; part 'get_reactions_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetReactionsResponse extends ApiResponse { class GetReactionsResponse extends ApiResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getReactionsResponse.dart'; part of 'get_reactions_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,16 +0,0 @@
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../talkApi.dart';
class LeaveRoom extends TalkApi {
String chatToken;
LeaveRoom(this.chatToken) : super('v4/room/$chatToken/participants/self', null);
@override
assemble(String raw) => null;
@override
Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.delete(uri, headers: headers);
}
@@ -1,9 +1,9 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'reactMessageParams.dart'; import 'react_message_params.dart';
class ReactMessage extends TalkApi { class ReactMessage extends TalkApi {
String chatToken; String chatToken;
@@ -11,7 +11,7 @@ class ReactMessage extends TalkApi {
ReactMessage({required this.chatToken, required this.messageId, required ReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params); ReactMessage({required this.chatToken, required this.messageId, required ReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params);
@override @override
assemble(String raw) => null; Null assemble(String raw) => null;
@override @override
Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) { Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) {
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'reactMessageParams.g.dart'; part 'react_message_params.g.dart';
@JsonSerializable() @JsonSerializable()
class ReactMessageParams extends ApiParams { class ReactMessageParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'reactMessageParams.dart'; part of 'react_message_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,23 +0,0 @@
import 'dart:convert';
import '../../../requestCache.dart';
import 'getRoom.dart';
import 'getRoomParams.dart';
import 'getRoomResponse.dart';
class GetRoomCache extends RequestCache<GetRoomResponse> {
GetRoomCache({onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
start('nc-rooms');
}
@override
GetRoomResponse onLocalData(String json) => GetRoomResponse.fromJson(jsonDecode(json));
@override
Future<GetRoomResponse> onLoad() => GetRoom(
GetRoomParams(
includeStatus: true,
)
).run();
}
@@ -2,9 +2,9 @@ import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../talkApi.dart'; import '../talk_api.dart';
import 'getRoomParams.dart'; import 'get_room_params.dart';
import 'getRoomResponse.dart'; import 'get_room_response.dart';
class GetRoom extends TalkApi<GetRoomResponse> { class GetRoom extends TalkApi<GetRoomResponse> {
@@ -14,7 +14,10 @@ class GetRoom extends TalkApi<GetRoomResponse> {
@override @override
GetRoomResponse assemble(String raw) => GetRoomResponse.fromJson(jsonDecode(raw)['ocs']); GetRoomResponse assemble(String raw) {
final decoded = jsonDecode(raw) as Map<String, dynamic>;
return GetRoomResponse.fromJson(decoded['ocs'] as Map<String, dynamic>);
}
@override @override
Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers); Future<http.Response> request(Uri uri, Object? body, Map<String, String>? headers) => http.get(uri, headers: headers);
@@ -0,0 +1,15 @@
import '../../../request_cache.dart';
import 'get_room.dart';
import 'get_room_params.dart';
import 'get_room_response.dart';
class GetRoomCache extends SimpleCache<GetRoomResponse> {
GetRoomCache({super.onUpdate, super.onError, super.renew})
: super(
cacheTime: RequestCache.cacheMinute,
loader: () => GetRoom(GetRoomParams(includeStatus: true)).run(),
fromJson: GetRoomResponse.fromJson,
) {
start('nc-rooms');
}
}
@@ -1,9 +1,9 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'getRoomParams.g.dart'; part 'get_room_params.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetRoomParams extends ApiParams { class GetRoomParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getRoomParams.dart'; part of 'get_room_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,9 +1,9 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiResponse.dart'; import '../../../api_response.dart';
import '../chat/getChatResponse.dart'; import '../chat/get_chat_response.dart';
part 'getRoomResponse.g.dart'; part 'get_room_response.g.dart';
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GetRoomResponse extends ApiResponse { class GetRoomResponse extends ApiResponse {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'getRoomResponse.dart'; part of 'get_room_response.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,5 +0,0 @@
import '../../../apiResponse.dart';
class SendMessageResponse extends ApiResponse {
}
@@ -1,16 +1,16 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'sendMessageParams.dart'; import 'send_message_params.dart';
class SendMessage extends TalkApi { class SendMessage extends TalkApi {
String chatToken; String chatToken;
SendMessage(this.chatToken, SendMessageParams params) : super('v1/chat/$chatToken', params); SendMessage(this.chatToken, SendMessageParams params) : super('v1/chat/$chatToken', params);
@override @override
assemble(String raw) => null; Null assemble(String raw) => null;
@override @override
Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) { Future<Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers) {
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'sendMessageParams.g.dart'; part 'send_message_params.g.dart';
@JsonSerializable(explicitToJson: true, includeIfNull: false) @JsonSerializable(explicitToJson: true, includeIfNull: false)
class SendMessageParams extends ApiParams { class SendMessageParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'sendMessageParams.dart'; part of 'send_message_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
@@ -1,25 +0,0 @@
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../talkApi.dart';
class SetFavorite extends TalkApi {
String chatToken;
bool favoriteState;
SetFavorite(this.chatToken, this.favoriteState) : super('v4/room/$chatToken/favorite', null);
@override
assemble(String raw) => null;
@override
Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) {
if(favoriteState) {
return http.post(uri, headers: headers);
} else {
return http.delete(uri, headers: headers);
}
}
}
@@ -2,8 +2,8 @@
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../talkApi.dart'; import '../talk_api.dart';
import 'setReadMarkerParams.dart'; import 'set_read_marker_params.dart';
class SetReadMarker extends TalkApi { class SetReadMarker extends TalkApi {
String chatToken; String chatToken;
@@ -15,7 +15,7 @@ class SetReadMarker extends TalkApi {
} }
@override @override
assemble(String raw) => null; Null assemble(String raw) => null;
@override @override
Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) { Future<Response> request(Uri uri, Object? body, Map<String, String>? headers) {
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart'; import '../../../api_params.dart';
part 'setReadMarkerParams.g.dart'; part 'set_read_marker_params.g.dart';
@JsonSerializable() @JsonSerializable()
class SetReadMarkerParams extends ApiParams { class SetReadMarkerParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'setReadMarkerParams.dart'; part of 'set_read_marker_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator
-66
View File
@@ -1,66 +0,0 @@
import 'dart:developer';
import 'package:http/http.dart' as http;
import '../../../model/accountData.dart';
import '../../../model/endpointData.dart';
import '../../apiError.dart';
import '../../apiParams.dart';
import '../../apiRequest.dart';
import '../../apiResponse.dart';
enum TalkApiMethod {
get,
post,
put,
delete,
}
abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
String path;
ApiParams? body;
Map<String, String>? headers = {};
Map<String, dynamic>? getParameters;
http.Response? response;
TalkApi(this.path, this.body, {this.headers, this.getParameters});
Future<http.Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers);
T assemble(String raw);
Future<T> run() async {
getParameters?.forEach((key, value) {
getParameters?.update(key, (value) => value.toString());
});
var endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/apps/spreed/api/$path', getParameters);
headers ??= {};
headers?.putIfAbsent('Accept', () => 'application/json');
headers?.putIfAbsent('OCS-APIRequest', () => 'true');
http.Response? data;
try {
data = await request(endpoint, body, headers);
if(data == null) throw Exception('No response Data');
if(data.statusCode >= 400 || data.statusCode < 200) throw Exception("Response status code '${data.statusCode}' might indicate an error");
} catch(e) {
log(e.toString());
throw ApiError('Request $endpoint could not be dispatched: ${e.toString()}');
}
//dynamic jsonData = jsonDecode(data.body);
T assembled;
try {
assembled = assemble(data.body);
assembled?.headers = data.headers;
return assembled;
} catch (e) {
var message = 'Error assembling Talk API ${T.toString()} message: ${e.toString()} response with request body: $body and request headers: ${headers.toString()}';
log(message);
throw Exception(message);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../../api_params.dart';
import '../../api_request.dart';
import '../../api_response.dart';
import '../../errors/auth_exception.dart';
import '../../errors/network_exception.dart';
import '../../errors/not_found_exception.dart';
import '../../errors/parse_exception.dart';
import '../../errors/server_exception.dart';
import '../nextcloud_ocs.dart';
enum TalkApiMethod {
get,
post,
put,
delete,
}
abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
String path;
ApiParams? body;
Map<String, String>? headers;
Map<String, dynamic>? getParameters;
http.Response? response;
TalkApi(this.path, this.body, {this.headers, this.getParameters});
Future<http.Response>? request(Uri uri, ApiParams? body, Map<String, String>? headers);
T assemble(String raw);
Future<T> run() async {
final endpoint = NextcloudOcs.uri('apps/spreed/api/$path', queryParameters: getParameters);
final mergedHeaders = {...NextcloudOcs.headers(), ...?headers};
final http.Response data;
try {
final raw = await request(endpoint, body, mergedHeaders);
if (raw == null) {
throw const NetworkException(
userMessage: 'Keine Antwort vom Talk-Server erhalten.',
technicalDetails: 'Talk request returned null',
);
}
data = raw;
} on SocketException catch (e) {
throw NetworkException(technicalDetails: 'Talk $endpoint: ${e.message}');
} on TimeoutException catch (e) {
throw NetworkException.timeout(technicalDetails: 'Talk $endpoint: $e');
} on http.ClientException catch (e) {
throw NetworkException(technicalDetails: 'Talk $endpoint: ${e.message}');
}
final status = data.statusCode;
if (status < 200 || status >= 300) {
final detail = 'Talk $endpoint -> HTTP $status';
log(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);
}
try {
final assembled = assemble(data.body);
assembled?.headers = data.headers;
return assembled;
} catch (e) {
throw ParseException(technicalDetails: 'Talk $endpoint assemble: $e');
}
}
}
@@ -1,26 +0,0 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import '../getPoll/getPollStateResponse.dart';
import '../talkApi.dart';
import 'votePollParams.dart';
@Deprecated('VotePoll is broken')
class VotePoll extends TalkApi {
String token;
int pollId;
VotePoll({required this.token, required this.pollId, required VotePollParams params}) : super('v1/poll/$token/$pollId', params);
@override
GetPollStateResponse assemble(String raw) => GetPollStateResponse.fromJson(jsonDecode(raw)['ocs']);
@override
Future<Response>? request(Uri uri, Object? body, Map<String, String>? headers) {
if(body is VotePollParams) {
return http.post(uri, headers: headers, body: body.toJson().toString());
}
return null;
}
}
@@ -1,15 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import '../../../apiParams.dart';
part 'votePollParams.g.dart';
@JsonSerializable()
@Deprecated('VotePoll is broken')
class VotePollParams extends ApiParams {
List<int> optionIds;
VotePollParams({required this.optionIds});
factory VotePollParams.fromJson(Map<String, dynamic> json) => _$VotePollParamsFromJson(json);
Map<String, dynamic> toJson() => _$VotePollParamsToJson(this);
}
@@ -1,17 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'votePollParams.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
VotePollParams _$VotePollParamsFromJson(Map<String, dynamic> json) =>
VotePollParams(
optionIds: (json['optionIds'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
);
Map<String, dynamic> _$VotePollParamsToJson(VotePollParams instance) =>
<String, dynamic>{'optionIds': instance.optionIds};
@@ -1,22 +0,0 @@
import '../../../../apiResponse.dart';
import '../../webdavApi.dart';
import 'downloadFileParams.dart';
class DownloadFile extends WebdavApi<DownloadFileParams> {
DownloadFileParams params;
DownloadFile(this.params) : super(params);
@override
Future<ApiResponse> run() async {
// final file = await File(localPath).create();
// Uint8List downloadedFile = await (await WebdavApi.webdav).download(params.webdavPath);
// file.writeAsBytesSync(downloadedFile, flush: true, mode: FileMode.write);
//
// OpenFile.open(localPath);
throw UnimplementedError();
}
}
@@ -0,0 +1,16 @@
import '../../../../api_response.dart';
import '../../webdav_api.dart';
import 'download_file_params.dart';
class DownloadFile extends WebdavApi<DownloadFileParams> {
DownloadFileParams params;
DownloadFile(this.params) : super(params);
@override
Future<ApiResponse> run() async {
throw UnimplementedError();
}
}
@@ -1,8 +1,8 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import '../../../../apiParams.dart'; import '../../../../api_params.dart';
part 'downloadFileParams.g.dart'; part 'download_file_params.g.dart';
@JsonSerializable() @JsonSerializable()
class DownloadFileParams extends ApiParams { class DownloadFileParams extends ApiParams {
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'downloadFileParams.dart'; part of 'download_file_params.dart';
// ************************************************************************** // **************************************************************************
// JsonSerializableGenerator // JsonSerializableGenerator

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