diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index b38194d..97f306b 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -2,6 +2,20 @@ + + - + + + + + + + + + + + + + - + + + + + - + + + + + + + - + + + + - + + + + + + + - + - + + - + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 68699cc..3e1b5a3 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,7 +1,6 @@ - @@ -11,12 +10,13 @@ - - - + + + + diff --git a/lib/api/apiError.dart b/lib/api/apiError.dart new file mode 100644 index 0000000..91cf533 --- /dev/null +++ b/lib/api/apiError.dart @@ -0,0 +1,5 @@ +class ApiError { + String message; + + ApiError(this.message); +} \ No newline at end of file diff --git a/lib/api/apiRequest.dart b/lib/api/apiRequest.dart new file mode 100644 index 0000000..c17ad0b --- /dev/null +++ b/lib/api/apiRequest.dart @@ -0,0 +1,39 @@ +import 'dart:developer'; + +import 'package:http/http.dart' as http; +import 'package:marianum_mobile/api/apiError.dart'; + +class ApiRequest { + Uri endpoint; + + ApiRequest(this.endpoint); + + Future get(Map? headers) async { + return await http.get(endpoint, headers: headers); + } + + Future post(String data, Map? headers) async { + log("Fetching: ${data}"); + try { + http.Response response = await http + .post(endpoint, body: data, headers: headers) + .timeout( + const Duration(seconds: 10), + onTimeout: () { + log("timeout!"); + throw ApiError("Network timeout"); + } + ); + + if(response.statusCode != 200) { + log("Got ${response.statusCode}"); + throw ApiError("Service response invalid, got status ${response.statusCode}"); + } + return response; + + } on Exception catch(e) { + throw ApiError("Http: ${e.toString()}"); + } + + } +} \ No newline at end of file diff --git a/lib/api/talk/requestLoginTest.dart b/lib/api/talk/requestLoginTest.dart new file mode 100644 index 0000000..724e63c --- /dev/null +++ b/lib/api/talk/requestLoginTest.dart @@ -0,0 +1,6 @@ +import 'package:marianum_mobile/api/apiRequest.dart'; + +class RequestLoginTest extends ApiRequest { + RequestLoginTest(super.endpoint); + +} \ No newline at end of file diff --git a/lib/api/webuntis/apiParams.dart b/lib/api/webuntis/apiParams.dart new file mode 100644 index 0000000..d59ae83 --- /dev/null +++ b/lib/api/webuntis/apiParams.dart @@ -0,0 +1,3 @@ +class ApiParams { + +} \ No newline at end of file diff --git a/lib/api/webuntis/apiResponse.dart b/lib/api/webuntis/apiResponse.dart new file mode 100644 index 0000000..7f670c5 --- /dev/null +++ b/lib/api/webuntis/apiResponse.dart @@ -0,0 +1,6 @@ +import 'package:http/http.dart' as http; +import 'package:json_annotation/json_annotation.dart'; +abstract class ApiResponse { + @JsonKey(includeFromJson: false, includeToJson: false) + late http.Response rawResponse; +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/authenticate/authenticate.dart b/lib/api/webuntis/queries/authenticate/authenticate.dart new file mode 100644 index 0000000..f6f8349 --- /dev/null +++ b/lib/api/webuntis/queries/authenticate/authenticate.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'authenticateParams.dart'; +import 'authenticateResponse.dart'; + +class Authenticate extends WebuntisApi { + AuthenticateParams param; + + Authenticate(this.param) : super("authenticate", param, authenticatedResponse: false); + + @override + Future run() async { + awaitingResponse = true; + String rawAnswer = await query(this); + AuthenticateResponse response = finalize(AuthenticateResponse.fromJson(jsonDecode(rawAnswer)['result'])); + _lastResponse = response; + awaitedResponse.complete(); + return response; + } + + static bool awaitingResponse = false; + static Completer awaitedResponse = Completer(); + static AuthenticateResponse? _lastResponse; + + static Future createSession() async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + + _lastResponse = await Authenticate( + AuthenticateParams( + user: preferences.getString("username")!, + password: preferences.getString("password")!, + ) + ).run(); + } + + static Future getSession() async { + if(awaitingResponse) { + log("Other query in progress... waiting"); + await awaitedResponse.future; + } + + if(_lastResponse == null) { + log("Not authenticated... requesting"); + awaitingResponse = true; + await createSession(); + } + return _lastResponse!; + + } +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/authenticate/authenticateParams.dart b/lib/api/webuntis/queries/authenticate/authenticateParams.dart new file mode 100644 index 0000000..30ce8e7 --- /dev/null +++ b/lib/api/webuntis/queries/authenticate/authenticateParams.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/webuntis/apiParams.dart'; + +part 'authenticateParams.g.dart'; + +@JsonSerializable() +class AuthenticateParams extends ApiParams { + + String user; + String password; + + AuthenticateParams({required this.user, required this.password}); + factory AuthenticateParams.fromJson(Map json) => _$AuthenticateParamsFromJson(json); + + Map toJson() => _$AuthenticateParamsToJson(this); +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/authenticate/authenticateParams.g.dart b/lib/api/webuntis/queries/authenticate/authenticateParams.g.dart new file mode 100644 index 0000000..49db5df --- /dev/null +++ b/lib/api/webuntis/queries/authenticate/authenticateParams.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'authenticateParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AuthenticateParams _$AuthenticateParamsFromJson(Map json) => + AuthenticateParams( + user: json['user'] as String, + password: json['password'] as String, + ); + +Map _$AuthenticateParamsToJson(AuthenticateParams instance) => + { + 'user': instance.user, + 'password': instance.password, + }; diff --git a/lib/api/webuntis/queries/authenticate/authenticateResponse.dart b/lib/api/webuntis/queries/authenticate/authenticateResponse.dart new file mode 100644 index 0000000..2ff4e0f --- /dev/null +++ b/lib/api/webuntis/queries/authenticate/authenticateResponse.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; + +part 'authenticateResponse.g.dart'; + +@JsonSerializable() +class AuthenticateResponse extends ApiResponse { + + String sessionId; + int personType; + int personId; + int klasseId; + + AuthenticateResponse(this.sessionId, this.personType, this.personId, this.klasseId); + + factory AuthenticateResponse.fromJson(Map json) => _$AuthenticateResponseFromJson(json); + Map toJson() => _$AuthenticateResponseToJson(this); +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/authenticate/authenticateResponse.g.dart b/lib/api/webuntis/queries/authenticate/authenticateResponse.g.dart new file mode 100644 index 0000000..3e53ed4 --- /dev/null +++ b/lib/api/webuntis/queries/authenticate/authenticateResponse.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'authenticateResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AuthenticateResponse _$AuthenticateResponseFromJson( + Map json) => + AuthenticateResponse( + json['sessionId'] as String, + json['personType'] as int, + json['personId'] as int, + json['klasseId'] as int, + ); + +Map _$AuthenticateResponseToJson( + AuthenticateResponse instance) => + { + 'sessionId': instance.sessionId, + 'personType': instance.personType, + 'personId': instance.personId, + 'klasseId': instance.klasseId, + }; diff --git a/lib/api/webuntis/queries/getRooms/getRooms.dart b/lib/api/webuntis/queries/getRooms/getRooms.dart new file mode 100644 index 0000000..58b303c --- /dev/null +++ b/lib/api/webuntis/queries/getRooms/getRooms.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; + +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; + +import 'getRoomsResponse.dart'; + +class GetRooms extends WebuntisApi { + GetRooms() : super("getRooms", null); + + @override + Future run() async { + String rawAnswer = await query(this); + return finalize(GetRoomsResponse.fromJson(jsonDecode(rawAnswer))); + } + +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart b/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart new file mode 100644 index 0000000..37d8689 --- /dev/null +++ b/lib/api/webuntis/queries/getRooms/getRoomsResponse.dart @@ -0,0 +1,29 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; + +part 'getRoomsResponse.g.dart'; + +@JsonSerializable() +class GetRoomsResponse extends ApiResponse { + Set result; + + GetRoomsResponse(this.result); + + factory GetRoomsResponse.fromJson(Map json) => _$GetRoomsResponseFromJson(json); + Map toJson() => _$GetRoomsResponseToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetRoomsResponseObject { + int id; + String name; + String longName; + bool active; + String building; + + + GetRoomsResponseObject(this.id, this.name, this.longName, this.active, this.building); + + factory GetRoomsResponseObject.fromJson(Map json) => _$GetRoomsResponseObjectFromJson(json); + Map toJson() => _$GetRoomsResponseObjectToJson(this); +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/getRooms/getRoomsResponse.g.dart b/lib/api/webuntis/queries/getRooms/getRoomsResponse.g.dart new file mode 100644 index 0000000..f2c2825 --- /dev/null +++ b/lib/api/webuntis/queries/getRooms/getRoomsResponse.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getRoomsResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetRoomsResponse _$GetRoomsResponseFromJson(Map json) => + GetRoomsResponse( + (json['result'] as List) + .map( + (e) => GetRoomsResponseObject.fromJson(e as Map)) + .toSet(), + ); + +Map _$GetRoomsResponseToJson(GetRoomsResponse instance) => + { + 'result': instance.result.toList(), + }; + +GetRoomsResponseObject _$GetRoomsResponseObjectFromJson( + Map json) => + GetRoomsResponseObject( + json['id'] as int, + json['name'] as String, + json['longName'] as String, + json['active'] as bool, + json['building'] as String, + ); + +Map _$GetRoomsResponseObjectToJson( + GetRoomsResponseObject instance) => + { + 'id': instance.id, + 'name': instance.name, + 'longName': instance.longName, + 'active': instance.active, + 'building': instance.building, + }; diff --git a/lib/api/webuntis/queries/getTimetable/getTimetable.dart b/lib/api/webuntis/queries/getTimetable/getTimetable.dart new file mode 100644 index 0000000..e1e1885 --- /dev/null +++ b/lib/api/webuntis/queries/getTimetable/getTimetable.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; +import 'package:marianum_mobile/api/webuntis/webuntisApi.dart'; + +import 'getTimetableParams.dart'; +import 'getTimetableResponse.dart'; + +class GetTimetable extends WebuntisApi { + GetTimetableParams params; + + GetTimetable(this.params) : super("getTimetable", params); + + @override + Future run() async { + String rawAnswer = await query(this); + log(rawAnswer); + return finalize(GetTimetableResponse.fromJson(jsonDecode(rawAnswer))); + } + +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart b/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart new file mode 100644 index 0000000..3a5ea76 --- /dev/null +++ b/lib/api/webuntis/queries/getTimetable/getTimetableParams.dart @@ -0,0 +1,91 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/webuntis/apiParams.dart'; + +part 'getTimetableParams.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetTimetableParams extends ApiParams { + GetTimetableParamsOptions options; + + GetTimetableParams({required this.options}); + + factory GetTimetableParams.fromJson(Map json) => _$GetTimetableParamsFromJson(json); + Map toJson() => _$GetTimetableParamsToJson(this); +} + + +@JsonSerializable(explicitToJson: true) +class GetTimetableParamsOptions { + GetTimetableParamsOptionsElement element; + @JsonKey(includeIfNull: false) + int? startDate; + @JsonKey(includeIfNull: false) + int? endDate; + @JsonKey(includeIfNull: false) + bool? onlyBaseTimetable; + @JsonKey(includeIfNull: false) + bool? showBooking; + @JsonKey(includeIfNull: false) + bool? showInfo; + @JsonKey(includeIfNull: false) + bool? showSubstText; + @JsonKey(includeIfNull: false) + bool? showLsText; + @JsonKey(includeIfNull: false) + bool? showLsNumber; + @JsonKey(includeIfNull: false) + bool? showStudentgroup; + @JsonKey(includeIfNull: false) + GetTimetableParamsOptionsFields? klasseFields; + @JsonKey(includeIfNull: false) + GetTimetableParamsOptionsFields? roomFields; + @JsonKey(includeIfNull: false) + GetTimetableParamsOptionsFields? subjectFields; + @JsonKey(includeIfNull: false) + GetTimetableParamsOptionsFields? teacherFields; + + GetTimetableParamsOptions({ + required this.element, + this.startDate, + this.endDate, + this.onlyBaseTimetable, + this.showBooking, + this.showInfo, + this.showSubstText, + this.showLsText, + this.showLsNumber, + this.showStudentgroup, + this.klasseFields, + this.roomFields, + this.subjectFields, + this.teacherFields + }); + + factory GetTimetableParamsOptions.fromJson(Map json) => _$GetTimetableParamsOptionsFromJson(json); + Map toJson() => _$GetTimetableParamsOptionsToJson(this); +} + +enum GetTimetableParamsOptionsFields { + @JsonValue("id") id, + @JsonValue("name") name, + @JsonValue("longname") longname, + @JsonValue("externalkey") externalkey, +} + +@JsonSerializable() +class GetTimetableParamsOptionsElement { + int id; + int type; + @JsonKey(includeIfNull: false) + GetTimetableParamsOptionsElementKeyType? keyType; + + GetTimetableParamsOptionsElement({required this.id, required this.type, this.keyType}); + factory GetTimetableParamsOptionsElement.fromJson(Map json) => _$GetTimetableParamsOptionsElementFromJson(json); + Map toJson() => _$GetTimetableParamsOptionsElementToJson(this); +} + +enum GetTimetableParamsOptionsElementKeyType { + @JsonValue("id") id, + @JsonValue("name") name, + @JsonValue("externalkey") externalkey +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart b/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart new file mode 100644 index 0000000..803512c --- /dev/null +++ b/lib/api/webuntis/queries/getTimetable/getTimetableParams.g.dart @@ -0,0 +1,114 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getTimetableParams.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetTimetableParams _$GetTimetableParamsFromJson(Map json) => + GetTimetableParams( + options: GetTimetableParamsOptions.fromJson( + json['options'] as Map), + ); + +Map _$GetTimetableParamsToJson(GetTimetableParams instance) => + { + 'options': instance.options.toJson(), + }; + +GetTimetableParamsOptions _$GetTimetableParamsOptionsFromJson( + Map json) => + GetTimetableParamsOptions( + element: GetTimetableParamsOptionsElement.fromJson( + json['element'] as Map), + startDate: json['startDate'] as int?, + endDate: json['endDate'] as int?, + onlyBaseTimetable: json['onlyBaseTimetable'] as bool?, + showBooking: json['showBooking'] as bool?, + showInfo: json['showInfo'] as bool?, + showSubstText: json['showSubstText'] as bool?, + showLsText: json['showLsText'] as bool?, + showLsNumber: json['showLsNumber'] as bool?, + showStudentgroup: json['showStudentgroup'] as bool?, + klasseFields: $enumDecodeNullable( + _$GetTimetableParamsOptionsFieldsEnumMap, json['klasseFields']), + roomFields: $enumDecodeNullable( + _$GetTimetableParamsOptionsFieldsEnumMap, json['roomFields']), + subjectFields: $enumDecodeNullable( + _$GetTimetableParamsOptionsFieldsEnumMap, json['subjectFields']), + teacherFields: $enumDecodeNullable( + _$GetTimetableParamsOptionsFieldsEnumMap, json['teacherFields']), + ); + +Map _$GetTimetableParamsOptionsToJson( + GetTimetableParamsOptions instance) { + final val = { + 'element': instance.element.toJson(), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('startDate', instance.startDate); + writeNotNull('endDate', instance.endDate); + writeNotNull('onlyBaseTimetable', instance.onlyBaseTimetable); + writeNotNull('showBooking', instance.showBooking); + writeNotNull('showInfo', instance.showInfo); + writeNotNull('showSubstText', instance.showSubstText); + writeNotNull('showLsText', instance.showLsText); + writeNotNull('showLsNumber', instance.showLsNumber); + writeNotNull('showStudentgroup', instance.showStudentgroup); + writeNotNull('klasseFields', + _$GetTimetableParamsOptionsFieldsEnumMap[instance.klasseFields]); + writeNotNull('roomFields', + _$GetTimetableParamsOptionsFieldsEnumMap[instance.roomFields]); + writeNotNull('subjectFields', + _$GetTimetableParamsOptionsFieldsEnumMap[instance.subjectFields]); + writeNotNull('teacherFields', + _$GetTimetableParamsOptionsFieldsEnumMap[instance.teacherFields]); + return val; +} + +const _$GetTimetableParamsOptionsFieldsEnumMap = { + GetTimetableParamsOptionsFields.id: 'id', + GetTimetableParamsOptionsFields.name: 'name', + GetTimetableParamsOptionsFields.longname: 'longname', + GetTimetableParamsOptionsFields.externalkey: 'externalkey', +}; + +GetTimetableParamsOptionsElement _$GetTimetableParamsOptionsElementFromJson( + Map json) => + GetTimetableParamsOptionsElement( + id: json['id'] as int, + type: json['type'] as int, + keyType: $enumDecodeNullable( + _$GetTimetableParamsOptionsElementKeyTypeEnumMap, json['keyType']), + ); + +Map _$GetTimetableParamsOptionsElementToJson( + GetTimetableParamsOptionsElement instance) { + final val = { + 'id': instance.id, + 'type': instance.type, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('keyType', + _$GetTimetableParamsOptionsElementKeyTypeEnumMap[instance.keyType]); + return val; +} + +const _$GetTimetableParamsOptionsElementKeyTypeEnumMap = { + GetTimetableParamsOptionsElementKeyType.id: 'id', + GetTimetableParamsOptionsElementKeyType.name: 'name', + GetTimetableParamsOptionsElementKeyType.externalkey: 'externalkey', +}; diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart new file mode 100644 index 0000000..547af3c --- /dev/null +++ b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.dart @@ -0,0 +1,85 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; + +part 'getTimetableResponse.g.dart'; + +@JsonSerializable(explicitToJson: true) +class GetTimetableResponse extends ApiResponse { + Set result; + + GetTimetableResponse(this.result); + + factory GetTimetableResponse.fromJson(Map json) => _$GetTimetableResponseFromJson(json); + Map toJson() => _$GetTimetableResponseToJson(this); + +} + +@JsonSerializable(explicitToJson: true) +class GetTimetableResponseObject { + int id; + int date; + int startTime; + int endTime; + String? lstype; + String? code; + String? info; + String? substText; + String? lstext; + int? lsnumber; + String? statflags; + String? activityType; + String? sg; + String? bkRemark; + String? bkText; + List kl; + List te; + List su; + List ro; + + GetTimetableResponseObject({ + required this.id, + required this.date, + required this.startTime, + required this.endTime, + this.lstype, + this.code, + this.info, + this.substText, + this.lstext, + this.lsnumber, + this.statflags, + this.activityType, + this.sg, + this.bkRemark, + required this.kl, + required this.te, + required this.su, + required this.ro + }); + + factory GetTimetableResponseObject.fromJson(Map json) => _$GetTimetableResponseObjectFromJson(json); + Map toJson() => _$GetTimetableResponseObjectToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GetTimetableResponseObjectFields { + List? te; + + GetTimetableResponseObjectFields(this.te); + + factory GetTimetableResponseObjectFields.fromJson(Map json) => _$GetTimetableResponseObjectFieldsFromJson(json); + Map toJson() => _$GetTimetableResponseObjectFieldsToJson(this); +} + +@JsonSerializable() +class GetTimetableResponseObjectFieldsObject { + int? id; + String? name; + String? longname; + String? externalkey; + + GetTimetableResponseObjectFieldsObject({this.id, this.name, this.longname, this.externalkey}); + + factory GetTimetableResponseObjectFieldsObject.fromJson(Map json) => _$GetTimetableResponseObjectFieldsObjectFromJson(json); + Map toJson() => _$GetTimetableResponseObjectFieldsObjectToJson(this); +} \ No newline at end of file diff --git a/lib/api/webuntis/queries/getTimetable/getTimetableResponse.g.dart b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.g.dart new file mode 100644 index 0000000..6df085e --- /dev/null +++ b/lib/api/webuntis/queries/getTimetable/getTimetableResponse.g.dart @@ -0,0 +1,103 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'getTimetableResponse.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GetTimetableResponse _$GetTimetableResponseFromJson( + Map json) => + GetTimetableResponse( + (json['result'] as List) + .map((e) => + GetTimetableResponseObject.fromJson(e as Map)) + .toSet(), + ); + +Map _$GetTimetableResponseToJson( + GetTimetableResponse instance) => + { + 'result': instance.result.map((e) => e.toJson()).toList(), + }; + +GetTimetableResponseObject _$GetTimetableResponseObjectFromJson( + Map json) => + GetTimetableResponseObject( + id: json['id'] as int, + date: json['date'] as int, + startTime: json['startTime'] as int, + endTime: json['endTime'] as int, + lstype: json['lstype'] as String?, + code: json['code'] as String?, + info: json['info'] as String?, + substText: json['substText'] as String?, + lstext: json['lstext'] as String?, + lsnumber: json['lsnumber'] as int?, + statflags: json['statflags'] as String?, + activityType: json['activityType'] as String?, + sg: json['sg'] as String?, + bkRemark: json['bkRemark'] as String?, + kl: json['kl'] as List, + te: json['te'] as List, + su: json['su'] as List, + ro: json['ro'] as List, + )..bkText = json['bkText'] as String?; + +Map _$GetTimetableResponseObjectToJson( + GetTimetableResponseObject instance) => + { + 'id': instance.id, + 'date': instance.date, + 'startTime': instance.startTime, + 'endTime': instance.endTime, + 'lstype': instance.lstype, + 'code': instance.code, + 'info': instance.info, + 'substText': instance.substText, + 'lstext': instance.lstext, + 'lsnumber': instance.lsnumber, + 'statflags': instance.statflags, + 'activityType': instance.activityType, + 'sg': instance.sg, + 'bkRemark': instance.bkRemark, + 'bkText': instance.bkText, + 'kl': instance.kl, + 'te': instance.te, + 'su': instance.su, + 'ro': instance.ro, + }; + +GetTimetableResponseObjectFields _$GetTimetableResponseObjectFieldsFromJson( + Map json) => + GetTimetableResponseObjectFields( + (json['te'] as List?) + ?.map((e) => GetTimetableResponseObjectFieldsObject.fromJson( + e as Map)) + .toList(), + ); + +Map _$GetTimetableResponseObjectFieldsToJson( + GetTimetableResponseObjectFields instance) => + { + 'te': instance.te?.map((e) => e.toJson()).toList(), + }; + +GetTimetableResponseObjectFieldsObject + _$GetTimetableResponseObjectFieldsObjectFromJson( + Map json) => + GetTimetableResponseObjectFieldsObject( + id: json['id'] as int?, + name: json['name'] as String?, + longname: json['longname'] as String?, + externalkey: json['externalkey'] as String?, + ); + +Map _$GetTimetableResponseObjectFieldsObjectToJson( + GetTimetableResponseObjectFieldsObject instance) => + { + 'id': instance.id, + 'name': instance.name, + 'longname': instance.longname, + 'externalkey': instance.externalkey, + }; diff --git a/lib/api/webuntis/webuntisApi.dart b/lib/api/webuntis/webuntisApi.dart new file mode 100644 index 0000000..5efae62 --- /dev/null +++ b/lib/api/webuntis/webuntisApi.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'package:marianum_mobile/api/apiRequest.dart'; +import 'package:http/http.dart' as http; +import 'package:marianum_mobile/api/webuntis/webuntisError.dart'; +import 'package:marianum_mobile/api/webuntis/apiResponse.dart'; + +import 'apiParams.dart'; +import 'queries/authenticate/authenticate.dart'; + +abstract class WebuntisApi extends ApiRequest { + String method; + ApiParams? genericParam; + http.Response? response; + + bool authenticatedResponse; + + WebuntisApi(this.method, this.genericParam, {this.authenticatedResponse = true}) : super(Uri.parse("https://peleus.webuntis.com/WebUntis/jsonrpc.do?school=marianum-fulda")); + + + Future query(WebuntisApi untis) async { + String query = '{"id":"ID","method":"$method","params":${untis._body()},"jsonrpc":"2.0"}'; + log(query); + + String sessionId = "0"; + if(authenticatedResponse) { + sessionId = (await Authenticate.getSession()).sessionId; + } + http.Response data = await post(query, {"Cookie": "JSESSIONID=$sessionId"}); + response = data; + + dynamic jsonData = jsonDecode(data.body); + if(jsonData['error'] != null) { + if(jsonData['error']['code'] == -8520) { + await Authenticate.createSession(); + this.query(untis); + } else { + throw WebuntisError(jsonData['error']['message'], jsonData['error']['code']); + } + } + return data.body; + } + + dynamic finalize(dynamic response) { + response.rawResponse = this.response!; + return response; + } + + Future run(); + + String _body() { + return genericParam == null ? "{}" : jsonEncode(genericParam); + } +} \ No newline at end of file diff --git a/lib/api/webuntis/webuntisError.dart b/lib/api/webuntis/webuntisError.dart new file mode 100644 index 0000000..edd1843 --- /dev/null +++ b/lib/api/webuntis/webuntisError.dart @@ -0,0 +1,10 @@ +class WebuntisError { + String message; + int code; + + WebuntisError(this.message, this.code); + + String toString() { + return "WebUntis ($code): $message"; + } +} \ No newline at end of file diff --git a/lib/app.dart b/lib/app.dart index 943152c..9066fa0 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:marianum_mobile/data/incommingPackets/talkNotificationsPacket.dart'; +import 'package:marianum_mobile/screen/pages/timetable/testTimetable.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -42,7 +43,7 @@ class _AppState extends State { PageView( controller: pageController, children: const [ - Timetable(), + TestTimeTable(), Talk(), Files(), Overhang(), diff --git a/lib/screen/login/login.dart b/lib/screen/login/login.dart index 11f8052..c333634 100644 --- a/lib/screen/login/login.dart +++ b/lib/screen/login/login.dart @@ -1,12 +1,19 @@ + import 'dart:io'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_login/flutter_login.dart'; +import 'package:marianum_mobile/api/apiError.dart'; +import 'package:marianum_mobile/api/webuntis/webuntisError.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../api/webuntis/queries/authenticate/authenticateParams.dart'; +import '../../api/webuntis/queries/authenticate/authenticate.dart'; import '../../data/accountModel.dart'; + class Login extends StatefulWidget { const Login({Key? key}) : super(key: key); @@ -15,32 +22,75 @@ class Login extends StatefulWidget { } class _LoginState extends State { - Duration get loginTime => const Duration(milliseconds: 2250); + bool displayDisclaimerText = true; - final Future _storage = SharedPreferences.getInstance(); - - String? checkInput(value){ - return (value ?? "").length < 5 ? "Eingabe zu kurz" : null; + String? _checkInput(value){ + return (value ?? "").length == 0 ? "Eingabe erforderlich" : null; } + Future _login(LoginData data) async { + SharedPreferences preferences = await SharedPreferences.getInstance(); + preferences.setBool("loggedIn", false); + + try { + await Authenticate( + AuthenticateParams( + user: data.name, + password: data.password, + ) + ).run().then((value) => { + log(value.sessionId) + }); + } on WebuntisError catch(e) { + return e.toString(); + } on ApiError catch(e) { + return e.toString(); + } + + setState(() { + displayDisclaimerText = false; + }); + + preferences.setBool("loggedIn", true); + preferences.setString("username", data.name); + preferences.setString("password", data.password); + + return null; + } + + Future _resetPassword(String name) { + return Future.delayed(Duration.zero).then((_) { + return "Diese Funktion steht nicht zur Verfügung!"; + }); + } @override Widget build(BuildContext context) { return FlutterLogin( logo: Image.file(File("assets/logo/icon.png")).image, - userValidator: checkInput, - passwordValidator: checkInput, + userValidator: _checkInput, + passwordValidator: _checkInput, + onSubmitAnimationCompleted: () => Provider.of(context, listen: false).login(), - onLogin: _authUser, + savedEmail: "test", + + onLogin: _login, onSignup: null, - onRecoverPassword: _recoverPassword, + + onRecoverPassword: _resetPassword, + hideForgotPasswordButton: true, theme: LoginTheme( primaryColor: Theme.of(context).primaryColor, + accentColor: Colors.white, + errorColor: Theme.of(context).primaryColor, + footerBottomPadding: 10, + textFieldStyle: const TextStyle( + fontWeight: FontWeight.w500 + ), cardTheme: const CardTheme( elevation: 10, - shape: InputBorder.none, ), ), @@ -52,42 +102,23 @@ class _LoginState extends State { disableCustomPageTransformer: true, - headerWidget: const Padding( - padding: EdgeInsets.only(bottom: 10), + headerWidget: Padding( + padding: const EdgeInsets.only(bottom: 30), child: Center( - child: Text( - "Dies ist ein Inoffizieller Nextclient & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nBitte bedenke, dass deine persönlichen Anmelde & Infodaten durch dritte, nicht vom Marianum betriebene, Systeme geleitet werden!", + child: Visibility( + visible: displayDisclaimerText, + child: const Text( + "Dies ist ein Inoffizieller Nextclient & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nKeinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!", textAlign: TextAlign.center, + ), ), ), ), - footer: "Marianum Fulda - Die persönliche Schule!", - title: "Marianum", + footer: "Marianum Fulda - Die persönliche Schule", + title: "Marianum Fulda", - hideForgotPasswordButton: true, userType: LoginUserType.name, - ); } - - Future _authUser(LoginData data) async { - final SharedPreferences preferences = await _storage; - preferences.setBool("loggedIn", true); - preferences.setString("username", data.name); - preferences.setString("password", data.password); - - debugPrint('Name: ${data.name}, Password: ${data.password}'); - return Future.delayed(loginTime).then((_) { - Provider.of(context, listen: false).login(); - return null; - }); - } - - Future _recoverPassword(String name) { - return Future.delayed(loginTime).then((_) { - return "Diese Funktion steht nicht zur Verfügung!"; - }); - } - } diff --git a/lib/screen/pages/timetable/testTimetable.dart b/lib/screen/pages/timetable/testTimetable.dart new file mode 100644 index 0000000..35f7b8d --- /dev/null +++ b/lib/screen/pages/timetable/testTimetable.dart @@ -0,0 +1,145 @@ +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:marianum_mobile/api/webuntis/queries/getRooms/getRooms.dart'; +import 'package:marianum_mobile/data/incommingPackets/timetablePacket.dart'; +import 'package:marianum_mobile/widget/loadingSpinner.dart'; +import 'package:marianum_mobile/widget/offlineError.dart'; +import 'package:timetable_view/timetable_view.dart'; + +import '../../../api/webuntis/queries/getTimetable/getTimetableParams.dart'; +import '../../../api/webuntis/queries/getTimetable/getTimetableResponse.dart'; +import '../../../api/webuntis/queries/getTimetable/getTimetable.dart'; + +class TestTimeTable extends StatefulWidget { + const TestTimeTable({Key? key}) : super(key: key); + + @override + State createState() => _TestTimeTableState(); +} + +class _TestTimeTableState extends State { + + late Future data; + + @override + void initState() { + data = GetTimetable( + GetTimetableParams( + options: GetTimetableParamsOptions( + element: GetTimetableParamsOptionsElement( + id: 92, + type: 5, + keyType: GetTimetableParamsOptionsElementKeyType.id, + ), + startDate: 20230206, + endDate: 20230212, + ) + ) + ).run(); + + + GetRooms().run().then((value) => { + log(value.rawResponse.body) + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + builder: (BuildContext context, AsyncSnapshot snapshot) { + if(snapshot.hasData) { + return Center( + child: TimetableView( + laneEventsList: _buildLaneEvents(snapshot.data!), + onEventTap: (TableEvent event) {}, + timetableStyle: CustomTableStyle(context), + onEmptySlotTap: (int laneIndex, TableEventTime start, TableEventTime end) => {}, + ), + ); + } else if(snapshot.hasError) { + return const OfflineBanner(text: "Der Stundenplan konnte nicht geladen werden!"); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + future: data, + ); + } + + List _buildLaneEvents(GetTimetableResponse data) { + List laneEvents = List.empty(growable: true); + Jiffy.locale("de"); + + List dayList = data.result.map((e) => e.date).toSet().toList(); + dayList.sort((a, b) => a-b); + dayList.forEach((day) { + //Every Day + + laneEvents.add( + LaneEvents( + lane: Lane( + laneIndex: day, + name: "${Jiffy(day.toString()).format("dd.MM.yy")}\n${Jiffy(day.toString()).format("EEEE")}", + textStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 14 + ) + ), + events: List.generate( + data.result.where((element) => element.date == day).length, + (index) { + GetTimetableResponseObject tableEvent = data.result.where((element) => element.date == day).elementAt(index); + return TableEvent( + title: "${tableEvent.substText}", + eventId: tableEvent.id, + laneIndex: day, + startTime: parseTime(tableEvent.startTime), + endTime: parseTime(tableEvent.endTime), + padding: const EdgeInsets.all(5), + backgroundColor: Theme.of(context).primaryColor, + location: "\n${tableEvent.statflags}", + ); + } + ) + ) + ); + + }); + + return laneEvents; + } + + TableEventTime parseTime(int input) { + String time = input.toString().length < 4 ? "0$input" : input.toString(); + return TableEventTime(hour: int.parse(time.substring(0, 2)), minute: int.parse(time.substring(2, 4))); + } +} + +class CustomTableStyle extends TimetableStyle { + dynamic context; + CustomTableStyle(this.context); + + @override + int get startHour => 07; + @override + int get endHour => 17; + @override + double get laneWidth => 100; + @override + Color get cornerColor => Theme.of(context).primaryColor; + @override + Color get timeItemTextColor => Theme.of(context).primaryColor; + @override + double get timeItemHeight => 60; + @override + double get timeItemWidth => 40; + +} \ No newline at end of file diff --git a/lib/widget/offlineError.dart b/lib/widget/offlineError.dart new file mode 100644 index 0000000..c723007 --- /dev/null +++ b/lib/widget/offlineError.dart @@ -0,0 +1,32 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OfflineBanner extends StatelessWidget { + final IconData icon; + final String text; + const OfflineBanner({Key? key, this.icon = Icons.report_gmailerrorred, this.text = "Es ist ein Fehler aufgetreten!"}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + margin: const EdgeInsets.only(top: 100, left: 20, right: 20), + child: Column( + children: [ + Container( + margin: const EdgeInsets.all(30), + child: Icon(icon, color: Colors.grey, size: 60), + ), + Text(text, + style: const TextStyle( + fontSize: 20, + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index a0e0e84..64a8842 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,10 +46,13 @@ dependencies: web_socket_channel: ^2.2.0 jiffy: ^5.0.0 timetable_view: ^0.3.0 + json_annotation: ^4.8.0 dev_dependencies: flutter_test: sdk: flutter + json_serializable: ^6.6.1 + build_runner: ^2.3.3 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is