migrated timetable integration from WebUntis to the MarianumConnect API, implementing a Dio-based client with bearer token authentication, background session validation, and auto-refresh logic.

This commit is contained in:
2026-05-23 17:32:42 +02:00
parent 2858f910c9
commit 93b9929f8f
106 changed files with 2739 additions and 2624 deletions
+87 -15
View File
@@ -6,7 +6,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../background/widget_background_task.dart';
import '../../state/app/modules/account/bloc/account_bloc.dart';
import '../../state/app/modules/account/bloc/account_state.dart';
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../storage/dev_tools_settings.dart';
import '../../storage/settings.dart' as model;
import '../../theming/light_app_theme.dart';
import '../pages/settings/widgets/endpoint_picker.dart';
import 'login_controller.dart';
import 'widgets/login_branding.dart';
import 'widgets/login_card.dart';
@@ -57,21 +61,33 @@ class _LoginState extends State<Login> {
minHeight: constraints.maxHeight,
maxWidth: 420,
),
child: IntrinsicHeight(
child: Column(
children: [
const LoginHeader(),
const SizedBox(height: 28),
LoginCard(
controller: _controller,
onSuccess: _onLoginSuccess,
),
const SizedBox(height: 18),
const LoginDisclaimer(),
const Spacer(),
const LoginFooter(),
],
),
// spaceBetween statt Spacer-in-IntrinsicHeight: bei jeder
// Inhaltsänderung im unteren Block (z.B. EndpointLink mit
// dynamischem Label) würde IntrinsicHeight sonst die Column
// an die intrinsic-Höhe pinnen und ein paar Pixel Overflow
// produzieren. spaceBetween fügt nur den verbleibenden Gap
// ein und schrumpft sauber auf 0, wenn der Inhalt zu hoch
// wird — dann übernimmt der äußere ScrollView.
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
const LoginHeader(),
const SizedBox(height: 28),
LoginCard(
controller: _controller,
onSuccess: _onLoginSuccess,
),
const SizedBox(height: 18),
const LoginDisclaimer(),
],
),
const Column(
mainAxisSize: MainAxisSize.min,
children: [_EndpointLink(), LoginFooter()],
),
],
),
),
),
@@ -80,3 +96,59 @@ class _LoginState extends State<Login> {
),
);
}
/// Subtle text link above the footer that surfaces the currently selected
/// Marianum-Connect endpoint and opens the picker on tap. Always visible so
/// devs can switch the endpoint before the first login without hunting for a
/// long-press easter egg, but understated enough not to draw regular users
/// into the dev menu.
class _EndpointLink extends StatelessWidget {
const _EndpointLink();
@override
Widget build(BuildContext context) =>
BlocBuilder<SettingsCubit, model.Settings>(
builder: (context, settings) {
final dev = settings.devToolsSettings;
final label = _label(dev);
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white.withValues(alpha: 0.85),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
minimumSize: const Size(0, 28),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
textStyle: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
),
),
onPressed: () => MarianumConnectEndpointPicker.show(
context,
context.read<SettingsCubit>(),
),
child: Text('Server: $label'),
),
);
},
);
static String _label(DevToolsSettings dev) {
switch (dev.marianumConnectEndpoint) {
case MarianumConnectEndpoint.live:
return 'Normal';
case MarianumConnectEndpoint.beta:
return 'Beta';
case MarianumConnectEndpoint.custom:
final url = DevToolsSettings.sanitizeCustomUrl(
dev.marianumConnectCustomUrl,
);
return url ?? 'Eigener Server (ungültig)';
}
}
}