removed timetable limits, fully dependent on marianumConnect now
This commit is contained in:
@@ -2,13 +2,16 @@ import '../../../state/app/modules/capabilities/bloc/capabilities_state.dart';
|
|||||||
import '../../../state/app/modules/nextcloud_capabilities/bloc/nextcloud_capabilities_state.dart';
|
import '../../../state/app/modules/nextcloud_capabilities/bloc/nextcloud_capabilities_state.dart';
|
||||||
|
|
||||||
/// Demo fixtures for the mobile capability flags — everything granted so the
|
/// Demo fixtures for the mobile capability flags — everything granted so the
|
||||||
/// demo persona sees every feature (incl. push) as available.
|
/// demo persona sees every feature (incl. push) as available and the timetable
|
||||||
|
/// scroll range stays unlimited (null day counts).
|
||||||
class DemoCapabilities {
|
class DemoCapabilities {
|
||||||
const DemoCapabilities._();
|
const DemoCapabilities._();
|
||||||
|
|
||||||
static CapabilitiesState state() => const CapabilitiesState(
|
static CapabilitiesState state() => const CapabilitiesState(
|
||||||
viewForeignTimetables: true,
|
viewForeignTimetables: true,
|
||||||
pushNotifications: true,
|
pushNotifications: true,
|
||||||
|
timetablePastDays: null,
|
||||||
|
timetableFutureDays: null,
|
||||||
loaded: true,
|
loaded: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,18 @@ class CapabilitiesResponse {
|
|||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
final bool pushNotifications;
|
final bool pushNotifications;
|
||||||
|
|
||||||
|
/// How many days into the past/future the user may view the timetable.
|
||||||
|
/// `null` (absent) means unlimited — the school year alone governs. The
|
||||||
|
/// backend widens both to at least cover the current Mon–Sun week.
|
||||||
|
final int? timetablePastDays;
|
||||||
|
|
||||||
|
final int? timetableFutureDays;
|
||||||
|
|
||||||
CapabilitiesResponse({
|
CapabilitiesResponse({
|
||||||
required this.viewForeignTimetables,
|
required this.viewForeignTimetables,
|
||||||
required this.pushNotifications,
|
required this.pushNotifications,
|
||||||
|
this.timetablePastDays,
|
||||||
|
this.timetableFutureDays,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory CapabilitiesResponse.fromJson(Map<String, dynamic> json) =>
|
factory CapabilitiesResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ CapabilitiesResponse _$CapabilitiesResponseFromJson(
|
|||||||
) => CapabilitiesResponse(
|
) => CapabilitiesResponse(
|
||||||
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
||||||
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
||||||
|
timetablePastDays: (json['timetablePastDays'] as num?)?.toInt(),
|
||||||
|
timetableFutureDays: (json['timetableFutureDays'] as num?)?.toInt(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$CapabilitiesResponseToJson(
|
Map<String, dynamic> _$CapabilitiesResponseToJson(
|
||||||
@@ -18,4 +20,6 @@ Map<String, dynamic> _$CapabilitiesResponseToJson(
|
|||||||
) => <String, dynamic>{
|
) => <String, dynamic>{
|
||||||
'viewForeignTimetables': instance.viewForeignTimetables,
|
'viewForeignTimetables': instance.viewForeignTimetables,
|
||||||
'pushNotifications': instance.pushNotifications,
|
'pushNotifications': instance.pushNotifications,
|
||||||
|
'timetablePastDays': instance.timetablePastDays,
|
||||||
|
'timetableFutureDays': instance.timetableFutureDays,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ class CapabilitiesCubit extends HydratedCubit<CapabilitiesState> {
|
|||||||
|
|
||||||
bool get canReceivePushNotifications => state.pushNotifications;
|
bool get canReceivePushNotifications => state.pushNotifications;
|
||||||
|
|
||||||
|
int? get timetablePastDays => state.timetablePastDays;
|
||||||
|
|
||||||
|
int? get timetableFutureDays => state.timetableFutureDays;
|
||||||
|
|
||||||
/// Refreshes capabilities from the server. On any failure (endpoint not yet
|
/// Refreshes capabilities from the server. On any failure (endpoint not yet
|
||||||
/// live, network error, 4xx) the previously hydrated flags are kept but the
|
/// live, network error, 4xx) the previously hydrated flags are kept but the
|
||||||
/// state is marked `loaded` — a failed fetch never silently grants a
|
/// state is marked `loaded` — a failed fetch never silently grants a
|
||||||
@@ -32,6 +36,8 @@ class CapabilitiesCubit extends HydratedCubit<CapabilitiesState> {
|
|||||||
CapabilitiesState(
|
CapabilitiesState(
|
||||||
viewForeignTimetables: response.viewForeignTimetables,
|
viewForeignTimetables: response.viewForeignTimetables,
|
||||||
pushNotifications: response.pushNotifications,
|
pushNotifications: response.pushNotifications,
|
||||||
|
timetablePastDays: response.timetablePastDays,
|
||||||
|
timetableFutureDays: response.timetableFutureDays,
|
||||||
loaded: true,
|
loaded: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ abstract class CapabilitiesState with _$CapabilitiesState {
|
|||||||
const factory CapabilitiesState({
|
const factory CapabilitiesState({
|
||||||
@Default(false) bool viewForeignTimetables,
|
@Default(false) bool viewForeignTimetables,
|
||||||
@Default(false) bool pushNotifications,
|
@Default(false) bool pushNotifications,
|
||||||
|
// Days into the past/future the timetable may be scrolled. Null = no
|
||||||
|
// client-side clamp; the (server-narrowed) school year alone governs.
|
||||||
|
int? timetablePastDays,
|
||||||
|
int? timetableFutureDays,
|
||||||
// Whether a capability response (or a definitive failure) has been
|
// Whether a capability response (or a definitive failure) has been
|
||||||
// observed at least once this session. Lets the UI distinguish "still
|
// observed at least once this session. Lets the UI distinguish "still
|
||||||
// unknown" from "confirmed not allowed".
|
// unknown" from "confirmed not allowed".
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$CapabilitiesState {
|
mixin _$CapabilitiesState {
|
||||||
|
|
||||||
bool get viewForeignTimetables; bool get pushNotifications;// Whether a capability response (or a definitive failure) has been
|
bool get viewForeignTimetables; bool get pushNotifications;// Days into the past/future the timetable may be scrolled. Null = no
|
||||||
|
// client-side clamp; the (server-narrowed) school year alone governs.
|
||||||
|
int? get timetablePastDays; int? get timetableFutureDays;// Whether a capability response (or a definitive failure) has been
|
||||||
// observed at least once this session. Lets the UI distinguish "still
|
// observed at least once this session. Lets the UI distinguish "still
|
||||||
// unknown" from "confirmed not allowed".
|
// unknown" from "confirmed not allowed".
|
||||||
bool get loaded;
|
bool get loaded;
|
||||||
@@ -31,16 +33,16 @@ $CapabilitiesStateCopyWith<CapabilitiesState> get copyWith => _$CapabilitiesStat
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is CapabilitiesState&&(identical(other.viewForeignTimetables, viewForeignTimetables) || other.viewForeignTimetables == viewForeignTimetables)&&(identical(other.pushNotifications, pushNotifications) || other.pushNotifications == pushNotifications)&&(identical(other.loaded, loaded) || other.loaded == loaded));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CapabilitiesState&&(identical(other.viewForeignTimetables, viewForeignTimetables) || other.viewForeignTimetables == viewForeignTimetables)&&(identical(other.pushNotifications, pushNotifications) || other.pushNotifications == pushNotifications)&&(identical(other.timetablePastDays, timetablePastDays) || other.timetablePastDays == timetablePastDays)&&(identical(other.timetableFutureDays, timetableFutureDays) || other.timetableFutureDays == timetableFutureDays)&&(identical(other.loaded, loaded) || other.loaded == loaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,viewForeignTimetables,pushNotifications,loaded);
|
int get hashCode => Object.hash(runtimeType,viewForeignTimetables,pushNotifications,timetablePastDays,timetableFutureDays,loaded);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CapabilitiesState(viewForeignTimetables: $viewForeignTimetables, pushNotifications: $pushNotifications, loaded: $loaded)';
|
return 'CapabilitiesState(viewForeignTimetables: $viewForeignTimetables, pushNotifications: $pushNotifications, timetablePastDays: $timetablePastDays, timetableFutureDays: $timetableFutureDays, loaded: $loaded)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ abstract mixin class $CapabilitiesStateCopyWith<$Res> {
|
|||||||
factory $CapabilitiesStateCopyWith(CapabilitiesState value, $Res Function(CapabilitiesState) _then) = _$CapabilitiesStateCopyWithImpl;
|
factory $CapabilitiesStateCopyWith(CapabilitiesState value, $Res Function(CapabilitiesState) _then) = _$CapabilitiesStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool viewForeignTimetables, bool pushNotifications, bool loaded
|
bool viewForeignTimetables, bool pushNotifications, int? timetablePastDays, int? timetableFutureDays, bool loaded
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -68,11 +70,13 @@ class _$CapabilitiesStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CapabilitiesState
|
/// Create a copy of CapabilitiesState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? viewForeignTimetables = null,Object? pushNotifications = null,Object? loaded = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? viewForeignTimetables = null,Object? pushNotifications = null,Object? timetablePastDays = freezed,Object? timetableFutureDays = freezed,Object? loaded = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
viewForeignTimetables: null == viewForeignTimetables ? _self.viewForeignTimetables : viewForeignTimetables // ignore: cast_nullable_to_non_nullable
|
viewForeignTimetables: null == viewForeignTimetables ? _self.viewForeignTimetables : viewForeignTimetables // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,pushNotifications: null == pushNotifications ? _self.pushNotifications : pushNotifications // ignore: cast_nullable_to_non_nullable
|
as bool,pushNotifications: null == pushNotifications ? _self.pushNotifications : pushNotifications // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,loaded: null == loaded ? _self.loaded : loaded // ignore: cast_nullable_to_non_nullable
|
as bool,timetablePastDays: freezed == timetablePastDays ? _self.timetablePastDays : timetablePastDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,timetableFutureDays: freezed == timetableFutureDays ? _self.timetableFutureDays : timetableFutureDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,loaded: null == loaded ? _self.loaded : loaded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -158,10 +162,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool viewForeignTimetables, bool pushNotifications, bool loaded)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool viewForeignTimetables, bool pushNotifications, int? timetablePastDays, int? timetableFutureDays, bool loaded)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CapabilitiesState() when $default != null:
|
case _CapabilitiesState() when $default != null:
|
||||||
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded);case _:
|
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.timetablePastDays,_that.timetableFutureDays,_that.loaded);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -179,10 +183,10 @@ return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool viewForeignTimetables, bool pushNotifications, bool loaded) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool viewForeignTimetables, bool pushNotifications, int? timetablePastDays, int? timetableFutureDays, bool loaded) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CapabilitiesState():
|
case _CapabilitiesState():
|
||||||
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded);case _:
|
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.timetablePastDays,_that.timetableFutureDays,_that.loaded);case _:
|
||||||
throw StateError('Unexpected subclass');
|
throw StateError('Unexpected subclass');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -199,10 +203,10 @@ return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool viewForeignTimetables, bool pushNotifications, bool loaded)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool viewForeignTimetables, bool pushNotifications, int? timetablePastDays, int? timetableFutureDays, bool loaded)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _CapabilitiesState() when $default != null:
|
case _CapabilitiesState() when $default != null:
|
||||||
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded);case _:
|
return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.timetablePastDays,_that.timetableFutureDays,_that.loaded);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -214,11 +218,15 @@ return $default(_that.viewForeignTimetables,_that.pushNotifications,_that.loaded
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _CapabilitiesState implements CapabilitiesState {
|
class _CapabilitiesState implements CapabilitiesState {
|
||||||
const _CapabilitiesState({this.viewForeignTimetables = false, this.pushNotifications = false, this.loaded = false});
|
const _CapabilitiesState({this.viewForeignTimetables = false, this.pushNotifications = false, this.timetablePastDays, this.timetableFutureDays, this.loaded = false});
|
||||||
factory _CapabilitiesState.fromJson(Map<String, dynamic> json) => _$CapabilitiesStateFromJson(json);
|
factory _CapabilitiesState.fromJson(Map<String, dynamic> json) => _$CapabilitiesStateFromJson(json);
|
||||||
|
|
||||||
@override@JsonKey() final bool viewForeignTimetables;
|
@override@JsonKey() final bool viewForeignTimetables;
|
||||||
@override@JsonKey() final bool pushNotifications;
|
@override@JsonKey() final bool pushNotifications;
|
||||||
|
// Days into the past/future the timetable may be scrolled. Null = no
|
||||||
|
// client-side clamp; the (server-narrowed) school year alone governs.
|
||||||
|
@override final int? timetablePastDays;
|
||||||
|
@override final int? timetableFutureDays;
|
||||||
// Whether a capability response (or a definitive failure) has been
|
// Whether a capability response (or a definitive failure) has been
|
||||||
// observed at least once this session. Lets the UI distinguish "still
|
// observed at least once this session. Lets the UI distinguish "still
|
||||||
// unknown" from "confirmed not allowed".
|
// unknown" from "confirmed not allowed".
|
||||||
@@ -237,16 +245,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CapabilitiesState&&(identical(other.viewForeignTimetables, viewForeignTimetables) || other.viewForeignTimetables == viewForeignTimetables)&&(identical(other.pushNotifications, pushNotifications) || other.pushNotifications == pushNotifications)&&(identical(other.loaded, loaded) || other.loaded == loaded));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CapabilitiesState&&(identical(other.viewForeignTimetables, viewForeignTimetables) || other.viewForeignTimetables == viewForeignTimetables)&&(identical(other.pushNotifications, pushNotifications) || other.pushNotifications == pushNotifications)&&(identical(other.timetablePastDays, timetablePastDays) || other.timetablePastDays == timetablePastDays)&&(identical(other.timetableFutureDays, timetableFutureDays) || other.timetableFutureDays == timetableFutureDays)&&(identical(other.loaded, loaded) || other.loaded == loaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,viewForeignTimetables,pushNotifications,loaded);
|
int get hashCode => Object.hash(runtimeType,viewForeignTimetables,pushNotifications,timetablePastDays,timetableFutureDays,loaded);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CapabilitiesState(viewForeignTimetables: $viewForeignTimetables, pushNotifications: $pushNotifications, loaded: $loaded)';
|
return 'CapabilitiesState(viewForeignTimetables: $viewForeignTimetables, pushNotifications: $pushNotifications, timetablePastDays: $timetablePastDays, timetableFutureDays: $timetableFutureDays, loaded: $loaded)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -257,7 +265,7 @@ abstract mixin class _$CapabilitiesStateCopyWith<$Res> implements $CapabilitiesS
|
|||||||
factory _$CapabilitiesStateCopyWith(_CapabilitiesState value, $Res Function(_CapabilitiesState) _then) = __$CapabilitiesStateCopyWithImpl;
|
factory _$CapabilitiesStateCopyWith(_CapabilitiesState value, $Res Function(_CapabilitiesState) _then) = __$CapabilitiesStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
bool viewForeignTimetables, bool pushNotifications, bool loaded
|
bool viewForeignTimetables, bool pushNotifications, int? timetablePastDays, int? timetableFutureDays, bool loaded
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -274,11 +282,13 @@ class __$CapabilitiesStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of CapabilitiesState
|
/// Create a copy of CapabilitiesState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? viewForeignTimetables = null,Object? pushNotifications = null,Object? loaded = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? viewForeignTimetables = null,Object? pushNotifications = null,Object? timetablePastDays = freezed,Object? timetableFutureDays = freezed,Object? loaded = null,}) {
|
||||||
return _then(_CapabilitiesState(
|
return _then(_CapabilitiesState(
|
||||||
viewForeignTimetables: null == viewForeignTimetables ? _self.viewForeignTimetables : viewForeignTimetables // ignore: cast_nullable_to_non_nullable
|
viewForeignTimetables: null == viewForeignTimetables ? _self.viewForeignTimetables : viewForeignTimetables // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,pushNotifications: null == pushNotifications ? _self.pushNotifications : pushNotifications // ignore: cast_nullable_to_non_nullable
|
as bool,pushNotifications: null == pushNotifications ? _self.pushNotifications : pushNotifications // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,loaded: null == loaded ? _self.loaded : loaded // ignore: cast_nullable_to_non_nullable
|
as bool,timetablePastDays: freezed == timetablePastDays ? _self.timetablePastDays : timetablePastDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,timetableFutureDays: freezed == timetableFutureDays ? _self.timetableFutureDays : timetableFutureDays // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,loaded: null == loaded ? _self.loaded : loaded // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ _CapabilitiesState _$CapabilitiesStateFromJson(Map<String, dynamic> json) =>
|
|||||||
_CapabilitiesState(
|
_CapabilitiesState(
|
||||||
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
||||||
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
||||||
|
timetablePastDays: (json['timetablePastDays'] as num?)?.toInt(),
|
||||||
|
timetableFutureDays: (json['timetableFutureDays'] as num?)?.toInt(),
|
||||||
loaded: json['loaded'] as bool? ?? false,
|
loaded: json['loaded'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -17,5 +19,7 @@ Map<String, dynamic> _$CapabilitiesStateToJson(_CapabilitiesState instance) =>
|
|||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'viewForeignTimetables': instance.viewForeignTimetables,
|
'viewForeignTimetables': instance.viewForeignTimetables,
|
||||||
'pushNotifications': instance.pushNotifications,
|
'pushNotifications': instance.pushNotifications,
|
||||||
|
'timetablePastDays': instance.timetablePastDays,
|
||||||
|
'timetableFutureDays': instance.timetableFutureDays,
|
||||||
'loaded': instance.loaded,
|
'loaded': instance.loaded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:syncfusion_flutter_calendar/calendar.dart';
|
|||||||
|
|
||||||
import '../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
import '../../../../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
|
||||||
import '../../../../extensions/date_time.dart';
|
import '../../../../extensions/date_time.dart';
|
||||||
|
import '../../../../state/app/modules/capabilities/bloc/capabilities_cubit.dart';
|
||||||
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||||
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
import '../../../../state/app/modules/timetable/bloc/timetable_state.dart';
|
||||||
import '../../../../storage/timetable_settings.dart';
|
import '../../../../storage/timetable_settings.dart';
|
||||||
@@ -105,7 +106,12 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
|
|||||||
disabledColor: Theme.of(context).disabledColor,
|
disabledColor: Theme.of(context).disabledColor,
|
||||||
).build();
|
).build();
|
||||||
|
|
||||||
final (minDate, maxDate) = _scrollBounds(state);
|
final capabilities = context.watch<CapabilitiesCubit>();
|
||||||
|
final (minDate, maxDate) = _scrollBounds(
|
||||||
|
state,
|
||||||
|
pastDays: capabilities.timetablePastDays,
|
||||||
|
futureDays: capabilities.timetableFutureDays,
|
||||||
|
);
|
||||||
|
|
||||||
return CustomWorkWeekCalendar(
|
return CustomWorkWeekCalendar(
|
||||||
key: _calendarKey,
|
key: _calendarKey,
|
||||||
@@ -122,17 +128,21 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hard caps applied on top of whatever Webuntis would allow. Even if the
|
|
||||||
/// school year (or a stale persisted bound) would let the user scroll
|
|
||||||
/// further, we never expose more than this much around the current week.
|
|
||||||
static const int _maxWeeksBack = 4;
|
|
||||||
static const int _maxWeeksForward = 2;
|
|
||||||
|
|
||||||
/// Returns the (minDate, maxDate) the user is allowed to scroll between.
|
/// Returns the (minDate, maxDate) the user is allowed to scroll between.
|
||||||
/// Starts from the Webuntis school year (or a tight window when that hasn't
|
/// Starts from the (server-narrowed) Webuntis school year — or a tight window
|
||||||
/// loaded yet), tightens by anything the bloc has learned from past denials,
|
/// when that hasn't loaded yet —, tightens by anything the bloc has learned
|
||||||
/// and finally clamps to a fixed window around today.
|
/// from past denials, and finally clamps to the window Connect grants via
|
||||||
(DateTime, DateTime) _scrollBounds(TimetableState state) {
|
/// [CapabilitiesCubit.timetablePastDays]/[CapabilitiesCubit.timetableFutureDays].
|
||||||
|
///
|
||||||
|
/// The capability clamp mirrors the server: `null` means unlimited (no client
|
||||||
|
/// clamp at all), and a given day count is widened to at least cover the
|
||||||
|
/// current Mon–Sun week — so the two windows always coincide and the clamp
|
||||||
|
/// can never invert.
|
||||||
|
(DateTime, DateTime) _scrollBounds(
|
||||||
|
TimetableState state, {
|
||||||
|
required int? pastDays,
|
||||||
|
required int? futureDays,
|
||||||
|
}) {
|
||||||
final year = state.schoolyear;
|
final year = state.schoolyear;
|
||||||
final DateTime baseMin;
|
final DateTime baseMin;
|
||||||
final DateTime baseMax;
|
final DateTime baseMax;
|
||||||
@@ -154,39 +164,43 @@ class TimetableCalendarViewState extends State<TimetableCalendarView> {
|
|||||||
? state.accessibleEndDate!
|
? state.accessibleEndDate!
|
||||||
: baseMax)
|
: baseMax)
|
||||||
: baseMax;
|
: baseMax;
|
||||||
final todayMonday = _mondayOf(DateTime.now());
|
final today = _startOfDay(DateTime.now());
|
||||||
final cappedMin = effectiveMin.isBefore(
|
final todayMonday = _mondayOf(today);
|
||||||
todayMonday.subtractDays(_maxWeeksBack * 7),
|
final currentWeekEnd = todayMonday.addDays(DateTime.daysPerWeek - 1);
|
||||||
)
|
final capMin = pastDays == null
|
||||||
? todayMonday.subtractDays(_maxWeeksBack * 7)
|
? null
|
||||||
|
: _earlier(today.subtractDays(pastDays), todayMonday);
|
||||||
|
final capMax = futureDays == null
|
||||||
|
? null
|
||||||
|
: _later(today.addDays(futureDays), currentWeekEnd);
|
||||||
|
final cappedMin = capMin != null && effectiveMin.isBefore(capMin)
|
||||||
|
? capMin
|
||||||
: effectiveMin;
|
: effectiveMin;
|
||||||
final cappedMax = effectiveMax.isAfter(
|
final cappedMax = capMax != null && effectiveMax.isAfter(capMax)
|
||||||
todayMonday.addDays(_maxWeeksForward * 7 + 6),
|
? capMax
|
||||||
)
|
|
||||||
? todayMonday.addDays(_maxWeeksForward * 7 + 6)
|
|
||||||
: effectiveMax;
|
: effectiveMax;
|
||||||
// When the resulting range does not cover the current week — the summer gap
|
// When the resulting range does not cover the current week — the summer gap
|
||||||
// between two school years, or a stale persisted bound — fall back to the
|
// between two school years, or a stale persisted bound — fall back to the
|
||||||
// full fixed window around today. Otherwise the PageView clamps the initial
|
// current week, widened to whatever the capabilities still allow. Otherwise
|
||||||
// page to the last week before the holidays (hiding the "Schulfrei" region)
|
// the PageView clamps the initial page to the last week before the holidays
|
||||||
// and forward scrolling collapses to the current week only.
|
// (hiding the "Schulfrei" region) and forward scrolling collapses to the
|
||||||
final currentWeekEnd = todayMonday.addDays(DateTime.daysPerWeek - 1);
|
// current week only.
|
||||||
final outsideRange =
|
final outsideRange =
|
||||||
cappedMax.isBefore(todayMonday) || cappedMin.isAfter(currentWeekEnd);
|
cappedMax.isBefore(todayMonday) || cappedMin.isAfter(currentWeekEnd);
|
||||||
final finalMin = outsideRange
|
final finalMin = outsideRange ? (capMin ?? todayMonday) : cappedMin;
|
||||||
? todayMonday.subtractDays(_maxWeeksBack * 7)
|
final finalMax = outsideRange ? (capMax ?? currentWeekEnd) : cappedMax;
|
||||||
: cappedMin;
|
|
||||||
final finalMax = outsideRange
|
|
||||||
? todayMonday.addDays(_maxWeeksForward * 7 + 6)
|
|
||||||
: cappedMax;
|
|
||||||
final daysToMonday =
|
final daysToMonday =
|
||||||
(DateTime.monday - finalMin.weekday) % DateTime.daysPerWeek;
|
(DateTime.monday - finalMin.weekday) % DateTime.daysPerWeek;
|
||||||
final mondayMin = finalMin.addDays(daysToMonday);
|
final mondayMin = finalMin.addDays(daysToMonday);
|
||||||
return (mondayMin, finalMax);
|
return (mondayMin, finalMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DateTime _mondayOf(DateTime d) {
|
static DateTime _mondayOf(DateTime d) =>
|
||||||
final monday = d.subtractDays(d.weekday - 1);
|
_startOfDay(d.subtractDays(d.weekday - 1));
|
||||||
return DateTime(monday.year, monday.month, monday.day);
|
|
||||||
}
|
static DateTime _startOfDay(DateTime d) => DateTime(d.year, d.month, d.day);
|
||||||
|
|
||||||
|
static DateTime _earlier(DateTime a, DateTime b) => a.isBefore(b) ? a : b;
|
||||||
|
|
||||||
|
static DateTime _later(DateTime a, DateTime b) => a.isAfter(b) ? a : b;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user