revamp on bloc approach

This commit is contained in:
2024-05-05 15:48:26 +02:00
parent ee6af2bc07
commit f58a2ec8cd
28 changed files with 523 additions and 480 deletions

@ -1,10 +0,0 @@
import '../../../infrastructure/controller.dart';
import 'grade_averages_state.dart';
class GradeAveragesController extends Controller<GradeAveragesState> {
GradeAveragesController(super.initialState);
void setGradeType(GradingSchemes scheme) => emit(state.copyWith(gradingScheme: scheme));
double average() => state.grades.reduce((a, b) => a + b) / state.grades.length;
}

@ -1,24 +0,0 @@
import '../../../infrastructure/controller.dart';
import '../../../infrastructure/loadable_state.dart';
import 'marianum_message_state.dart';
class MarianumMessageController extends Controller<LoadableState<MarianumMessageState>> {
MarianumMessageController() : super(const LoadableState(loadingState: LoadingState.none, data: MarianumMessageState(test: [])));
void loading() {
emit(state.loading());
Future.delayed(const Duration(seconds: 3)).then((value) => emit(state.done(const MarianumMessageState(test: []))));
}
void backgroundLoading() {
emit(state.cached(const MarianumMessageState(test: [])));
}
void done() {
emit(state.done(const MarianumMessageState(test: [])));
}
void error() {
emit(state.error(state: const MarianumMessageState(test: [])));
}
}

@ -1,10 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'marianum_message_state.freezed.dart';
@freezed
class MarianumMessageState with _$MarianumMessageState {
const factory MarianumMessageState({
required List<String> test
}) = _MarianumMessageState;
}

@ -1,143 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'marianum_message_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$MarianumMessageState {
List<String> get test => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$MarianumMessageStateCopyWith<MarianumMessageState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MarianumMessageStateCopyWith<$Res> {
factory $MarianumMessageStateCopyWith(MarianumMessageState value,
$Res Function(MarianumMessageState) then) =
_$MarianumMessageStateCopyWithImpl<$Res, MarianumMessageState>;
@useResult
$Res call({List<String> test});
}
/// @nodoc
class _$MarianumMessageStateCopyWithImpl<$Res,
$Val extends MarianumMessageState>
implements $MarianumMessageStateCopyWith<$Res> {
_$MarianumMessageStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? test = null,
}) {
return _then(_value.copyWith(
test: null == test
? _value.test
: test // ignore: cast_nullable_to_non_nullable
as List<String>,
) as $Val);
}
}
/// @nodoc
abstract class _$$MarianumMessageStateImplCopyWith<$Res>
implements $MarianumMessageStateCopyWith<$Res> {
factory _$$MarianumMessageStateImplCopyWith(_$MarianumMessageStateImpl value,
$Res Function(_$MarianumMessageStateImpl) then) =
__$$MarianumMessageStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<String> test});
}
/// @nodoc
class __$$MarianumMessageStateImplCopyWithImpl<$Res>
extends _$MarianumMessageStateCopyWithImpl<$Res, _$MarianumMessageStateImpl>
implements _$$MarianumMessageStateImplCopyWith<$Res> {
__$$MarianumMessageStateImplCopyWithImpl(_$MarianumMessageStateImpl _value,
$Res Function(_$MarianumMessageStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? test = null,
}) {
return _then(_$MarianumMessageStateImpl(
test: null == test
? _value._test
: test // ignore: cast_nullable_to_non_nullable
as List<String>,
));
}
}
/// @nodoc
class _$MarianumMessageStateImpl implements _MarianumMessageState {
const _$MarianumMessageStateImpl({required final List<String> test})
: _test = test;
final List<String> _test;
@override
List<String> get test {
if (_test is EqualUnmodifiableListView) return _test;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_test);
}
@override
String toString() {
return 'MarianumMessageState(test: $test)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MarianumMessageStateImpl &&
const DeepCollectionEquality().equals(other._test, _test));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_test));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
get copyWith =>
__$$MarianumMessageStateImplCopyWithImpl<_$MarianumMessageStateImpl>(
this, _$identity);
}
abstract class _MarianumMessageState implements MarianumMessageState {
const factory _MarianumMessageState({required final List<String> test}) =
_$MarianumMessageStateImpl;
@override
List<String> get test;
@override
@JsonKey(ignore: true)
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import '../../../view/pages/files/files.dart';
import '../../../view/pages/talk/chatList.dart';
import '../../../view/pages/timetable/timetable.dart';
class AppModule {
String name;
IconData icon;
Widget Function() create;
AppModule(this.name, this.icon, this.create);
static Map<Module, AppModule> modules() => {
Module.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new),
Module.talk: AppModule('Talk', Icons.chat, ChatList.new),
Module.files: AppModule('Files', Icons.folder, Files.new),
};
}
enum Module {
timetable,
talk,
files,
marianumMessage,
roomPlan,
gradeAveragesCalculator,
holidays,
}

@ -0,0 +1,49 @@
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'grade_averages_event.dart';
import 'grade_averages_state.dart';
class GradeAveragesBloc extends HydratedBloc<GradeAveragesEvent, GradeAveragesState> {
GradeAveragesBloc() : super(const GradeAveragesState(gradingSystem: GradeAveragesGradingSystem.middleSchool, grades: [])) {
on<GradingSystemChanged>((event, emit) {
add(ResetAll());
emit(
state.copyWith(
gradingSystem: event.isMiddleSchool
? GradeAveragesGradingSystem.middleSchool
: GradeAveragesGradingSystem.highSchool
)
);
});
on<ResetAll>((event, emit) {
emit(state.copyWith(grades: []));
});
on<ResetGrade>((event, emit) {
emit(state.copyWith(grades: [...state.grades]..removeWhere((grade) => grade == event.grade)));
});
on<IncrementGrade>((event, emit) {
emit(state.copyWith(grades: [...state.grades, event.grade]));
});
on<DecrementGrade>((event, emit) {
emit(state.copyWith(grades: List.from(state.grades)..remove(event.grade)));
});
}
double average() => state.grades.isEmpty ? 0 : state.grades.reduce((a, b) => a + b) / state.grades.length;
bool isMiddleSchool() => state.gradingSystem == GradeAveragesGradingSystem.middleSchool;
bool canDecrementOrDelete(int grade) => state.grades.contains(grade);
int countOfGrade(int grade) => state.grades.where((g) => g == grade).length;
int gradesInGradingSystem() => state.gradingSystem == GradeAveragesGradingSystem.middleSchool ? 6 : 16;
int getGradeFromIndex(int index) => isMiddleSchool() ? index + 1 : 15 - index;
@override
GradeAveragesState? fromJson(Map<String, dynamic> json) => GradeAveragesState.fromJson(json);
@override
Map<String, dynamic>? toJson(GradeAveragesState state) => state.toJson();
}

@ -0,0 +1,20 @@
sealed class GradeAveragesEvent {}
final class GradingSystemChanged extends GradeAveragesEvent {
final bool isMiddleSchool;
GradingSystemChanged(this.isMiddleSchool);
}
final class ResetAll extends GradeAveragesEvent {}
final class ResetGrade extends GradeAveragesEvent {
final int grade;
ResetGrade(this.grade);
}
final class IncrementGrade extends GradeAveragesEvent {
final int grade;
IncrementGrade(this.grade);
}
final class DecrementGrade extends GradeAveragesEvent {
final int grade;
DecrementGrade(this.grade);
}

@ -6,14 +6,14 @@ part 'grade_averages_state.g.dart';
@freezed
class GradeAveragesState with _$GradeAveragesState {
const factory GradeAveragesState({
required GradingSchemes gradingScheme,
required List<int> grades,
required GradeAveragesGradingSystem gradingSystem,
required List<int> grades,
}) = _GradeAveragesState;
factory GradeAveragesState.fromJson(Map<String, dynamic> json) => _$GradeAveragesStateFromJson(json);
}
enum GradingSchemes {
middleSchool,
enum GradeAveragesGradingSystem {
highSchool,
middleSchool,
}

@ -20,7 +20,8 @@ GradeAveragesState _$GradeAveragesStateFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$GradeAveragesState {
GradingSchemes get gradingScheme => throw _privateConstructorUsedError;
GradeAveragesGradingSystem get gradingSystem =>
throw _privateConstructorUsedError;
List<int> get grades => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -35,7 +36,7 @@ abstract class $GradeAveragesStateCopyWith<$Res> {
GradeAveragesState value, $Res Function(GradeAveragesState) then) =
_$GradeAveragesStateCopyWithImpl<$Res, GradeAveragesState>;
@useResult
$Res call({GradingSchemes gradingScheme, List<int> grades});
$Res call({GradeAveragesGradingSystem gradingSystem, List<int> grades});
}
/// @nodoc
@ -51,14 +52,14 @@ class _$GradeAveragesStateCopyWithImpl<$Res, $Val extends GradeAveragesState>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? gradingScheme = null,
Object? gradingSystem = null,
Object? grades = null,
}) {
return _then(_value.copyWith(
gradingScheme: null == gradingScheme
? _value.gradingScheme
: gradingScheme // ignore: cast_nullable_to_non_nullable
as GradingSchemes,
gradingSystem: null == gradingSystem
? _value.gradingSystem
: gradingSystem // ignore: cast_nullable_to_non_nullable
as GradeAveragesGradingSystem,
grades: null == grades
? _value.grades
: grades // ignore: cast_nullable_to_non_nullable
@ -75,7 +76,7 @@ abstract class _$$GradeAveragesStateImplCopyWith<$Res>
__$$GradeAveragesStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({GradingSchemes gradingScheme, List<int> grades});
$Res call({GradeAveragesGradingSystem gradingSystem, List<int> grades});
}
/// @nodoc
@ -89,14 +90,14 @@ class __$$GradeAveragesStateImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? gradingScheme = null,
Object? gradingSystem = null,
Object? grades = null,
}) {
return _then(_$GradeAveragesStateImpl(
gradingScheme: null == gradingScheme
? _value.gradingScheme
: gradingScheme // ignore: cast_nullable_to_non_nullable
as GradingSchemes,
gradingSystem: null == gradingSystem
? _value.gradingSystem
: gradingSystem // ignore: cast_nullable_to_non_nullable
as GradeAveragesGradingSystem,
grades: null == grades
? _value._grades
: grades // ignore: cast_nullable_to_non_nullable
@ -109,14 +110,14 @@ class __$$GradeAveragesStateImplCopyWithImpl<$Res>
@JsonSerializable()
class _$GradeAveragesStateImpl implements _GradeAveragesState {
const _$GradeAveragesStateImpl(
{required this.gradingScheme, required final List<int> grades})
{required this.gradingSystem, required final List<int> grades})
: _grades = grades;
factory _$GradeAveragesStateImpl.fromJson(Map<String, dynamic> json) =>
_$$GradeAveragesStateImplFromJson(json);
@override
final GradingSchemes gradingScheme;
final GradeAveragesGradingSystem gradingSystem;
final List<int> _grades;
@override
List<int> get grades {
@ -127,7 +128,7 @@ class _$GradeAveragesStateImpl implements _GradeAveragesState {
@override
String toString() {
return 'GradeAveragesState(gradingScheme: $gradingScheme, grades: $grades)';
return 'GradeAveragesState(gradingSystem: $gradingSystem, grades: $grades)';
}
@override
@ -135,15 +136,15 @@ class _$GradeAveragesStateImpl implements _GradeAveragesState {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$GradeAveragesStateImpl &&
(identical(other.gradingScheme, gradingScheme) ||
other.gradingScheme == gradingScheme) &&
(identical(other.gradingSystem, gradingSystem) ||
other.gradingSystem == gradingSystem) &&
const DeepCollectionEquality().equals(other._grades, _grades));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, gradingScheme, const DeepCollectionEquality().hash(_grades));
runtimeType, gradingSystem, const DeepCollectionEquality().hash(_grades));
@JsonKey(ignore: true)
@override
@ -162,14 +163,14 @@ class _$GradeAveragesStateImpl implements _GradeAveragesState {
abstract class _GradeAveragesState implements GradeAveragesState {
const factory _GradeAveragesState(
{required final GradingSchemes gradingScheme,
{required final GradeAveragesGradingSystem gradingSystem,
required final List<int> grades}) = _$GradeAveragesStateImpl;
factory _GradeAveragesState.fromJson(Map<String, dynamic> json) =
_$GradeAveragesStateImpl.fromJson;
@override
GradingSchemes get gradingScheme;
GradeAveragesGradingSystem get gradingSystem;
@override
List<int> get grades;
@override

@ -9,19 +9,20 @@ part of 'grade_averages_state.dart';
_$GradeAveragesStateImpl _$$GradeAveragesStateImplFromJson(
Map<String, dynamic> json) =>
_$GradeAveragesStateImpl(
gradingScheme:
$enumDecode(_$GradingSchemesEnumMap, json['gradingScheme']),
gradingSystem: $enumDecode(
_$GradeAveragesGradingSystemEnumMap, json['gradingSystem']),
grades: (json['grades'] as List<dynamic>).map((e) => e as int).toList(),
);
Map<String, dynamic> _$$GradeAveragesStateImplToJson(
_$GradeAveragesStateImpl instance) =>
<String, dynamic>{
'gradingScheme': _$GradingSchemesEnumMap[instance.gradingScheme]!,
'gradingSystem':
_$GradeAveragesGradingSystemEnumMap[instance.gradingSystem]!,
'grades': instance.grades,
};
const _$GradingSchemesEnumMap = {
GradingSchemes.middleSchool: 'middleSchool',
GradingSchemes.highSchool: 'highSchool',
const _$GradeAveragesGradingSystemEnumMap = {
GradeAveragesGradingSystem.highSchool: 'highSchool',
GradeAveragesGradingSystem.middleSchool: 'middleSchool',
};

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../bloc/grade_averages_bloc.dart';
import '../bloc/grade_averages_event.dart';
class GradeAveragesListView extends StatelessWidget {
const GradeAveragesListView({super.key});
@override
Widget build(BuildContext context) {
var bloc = context.watch<GradeAveragesBloc>();
String getGradeDisplay(int grade) {
if(bloc.isMiddleSchool()) {
return 'Note $grade';
} else {
return "$grade Punkt${grade > 1 ? "e" : ""}";
}
}
return ListView.builder(
itemCount: bloc.gradesInGradingSystem(),
itemBuilder: (context, index) {
var grade = bloc.getGradeFromIndex(index);
return Material(
child: ListTile(
tileColor: grade.isEven ? Colors.transparent : Colors.transparent.withAlpha(50),
title: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(getGradeDisplay(grade)),
const SizedBox(width: 30),
IconButton(
onPressed: () {
bloc.add(DecrementGrade(grade));
},
icon: const Icon(Icons.remove),
color: Theme.of(context).colorScheme.onSurface,
),
Text('${bloc.countOfGrade(grade)}', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
IconButton(
onPressed: () {
bloc.add(IncrementGrade(grade));
},
icon: const Icon(Icons.add),
color: Theme.of(context).colorScheme.onSurface,
),
],
),
),
trailing: Visibility(
maintainState: true,
maintainAnimation: true,
maintainSize: true,
visible: bloc.canDecrementOrDelete(grade),
child: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
bloc.add(ResetGrade(grade));
},
),
),
),
);
},
);
}
}

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../widget/confirmDialog.dart';
import '../bloc/grade_averages_bloc.dart';
import '../bloc/grade_averages_event.dart';
import '../bloc/grade_averages_state.dart';
import 'grade_averages_list_view.dart';
class GradeAveragesScreen extends StatelessWidget {
const GradeAveragesScreen({super.key});
@override
Widget build(BuildContext context) => BlocProvider<GradeAveragesBloc>(
create: (context) => GradeAveragesBloc(),
child: BlocBuilder<GradeAveragesBloc, GradeAveragesState>(
builder: (context, state) {
var bloc = context.watch<GradeAveragesBloc>();
return Scaffold(
appBar: AppBar(
title: const Text('Notendurschnittsrechner'),
actions: [
Visibility(
visible: bloc.state.grades.isNotEmpty,
child: IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: 'Zurücksetzen?',
content: 'Alle Einträge werden entfernt.',
confirmButton: 'Zurücksetzen',
onConfirm: () {
bloc.add(ResetAll());
},
),
);
},
icon: const Icon(Icons.delete_forever)),
),
PopupMenuButton<bool>(
initialValue: bloc.isMiddleSchool(),
icon: const Icon(Icons.more_horiz),
itemBuilder: (context) => [true, false].map((isMiddleSchool) => PopupMenuItem<bool>(
value: isMiddleSchool,
child: Row(
children: [
Icon(
isMiddleSchool ? Icons.calculate_outlined : Icons.school_outlined,
color: Theme.of(context).colorScheme.onSurface
),
const SizedBox(width: 15),
Text(isMiddleSchool ? 'Notensystem' : 'Punktesystem'),
],
),
)).toList(),
onSelected: (isMiddleSchool) {
if (bloc.state.grades.isNotEmpty) {
showDialog(
context: context,
builder: (context) => ConfirmDialog(
title: 'Notensystem wechseln',
content:
'Beim wechsel des Notensystems werden alle Einträge zurückgesetzt.',
confirmButton: 'Fortfahren',
onConfirm: () => bloc.add(GradingSystemChanged(isMiddleSchool)),
),
);
} else {
bloc.add(GradingSystemChanged(isMiddleSchool));
}
},
),
],
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 30),
Text(bloc.average().toStringAsFixed(2), style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 10),
Text(bloc.isMiddleSchool() ? 'Wähle unten die Anzahl deiner jewiligen Noten aus' : 'Wähle unten die Anzahl deiner jeweiligen Punkte aus'),
const SizedBox(height: 10),
const Expanded(
child: GradeAveragesListView()
),
],
),
);
},
),
);
}