Merge pull request 'develop-bloc' (#68) from develop-bloc into develop
Reviewed-on: #68 Reviewed-by: Pupsi <larslukasneuhaus@gmx.de>
This commit is contained in:
commit
43471fcf3d
@ -9,6 +9,10 @@
|
|||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
invalid_annotation_target: ignore
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
7
build.yaml
Normal file
7
build.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
json_serializable:
|
||||||
|
options:
|
||||||
|
explicit_to_json: false
|
||||||
|
generic_argument_factories: true
|
@ -1,17 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import '../../mhslApi.dart';
|
|
||||||
import 'getMessagesResponse.dart';
|
|
||||||
|
|
||||||
class GetMessages extends MhslApi<GetMessagesResponse> {
|
|
||||||
GetMessages() : super('message/messages.json');
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
GetMessagesResponse assemble(String raw) => GetMessagesResponse.fromJson(jsonDecode(raw));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<http.Response> request(Uri uri) => http.get(uri);
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import '../../../requestCache.dart';
|
|
||||||
import 'getMessages.dart';
|
|
||||||
import 'getMessagesResponse.dart';
|
|
||||||
|
|
||||||
class GetMessagesCache extends RequestCache<GetMessagesResponse> {
|
|
||||||
GetMessagesCache({onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
|
|
||||||
start('message');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
GetMessagesResponse onLocalData(String json) => GetMessagesResponse.fromJson(jsonDecode(json));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<GetMessagesResponse> onLoad() => GetMessages().run();
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
import '../../../apiResponse.dart';
|
|
||||||
|
|
||||||
part 'getMessagesResponse.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
|
||||||
class GetMessagesResponse extends ApiResponse {
|
|
||||||
String base;
|
|
||||||
Set<GetMessagesResponseObject> messages;
|
|
||||||
|
|
||||||
GetMessagesResponse(this.base, this.messages);
|
|
||||||
|
|
||||||
factory GetMessagesResponse.fromJson(Map<String, dynamic> json) => _$GetMessagesResponseFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$GetMessagesResponseToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
|
||||||
class GetMessagesResponseObject {
|
|
||||||
String name;
|
|
||||||
String date;
|
|
||||||
String url;
|
|
||||||
|
|
||||||
GetMessagesResponseObject(this.name, this.date, this.url);
|
|
||||||
|
|
||||||
factory GetMessagesResponseObject.fromJson(Map<String, dynamic> json) => _$GetMessagesResponseObjectFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$GetMessagesResponseObjectToJson(this);
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'getMessagesResponse.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
GetMessagesResponse _$GetMessagesResponseFromJson(Map<String, dynamic> json) =>
|
|
||||||
GetMessagesResponse(
|
|
||||||
json['base'] as String,
|
|
||||||
(json['messages'] as List<dynamic>)
|
|
||||||
.map((e) =>
|
|
||||||
GetMessagesResponseObject.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toSet(),
|
|
||||||
)..headers = (json['headers'] as Map<String, dynamic>?)?.map(
|
|
||||||
(k, e) => MapEntry(k, e as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$GetMessagesResponseToJson(GetMessagesResponse instance) {
|
|
||||||
final val = <String, dynamic>{};
|
|
||||||
|
|
||||||
void writeNotNull(String key, dynamic value) {
|
|
||||||
if (value != null) {
|
|
||||||
val[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeNotNull('headers', instance.headers);
|
|
||||||
val['base'] = instance.base;
|
|
||||||
val['messages'] = instance.messages.map((e) => e.toJson()).toList();
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetMessagesResponseObject _$GetMessagesResponseObjectFromJson(
|
|
||||||
Map<String, dynamic> json) =>
|
|
||||||
GetMessagesResponseObject(
|
|
||||||
json['name'] as String,
|
|
||||||
json['date'] as String,
|
|
||||||
json['url'] as String,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$GetMessagesResponseObjectToJson(
|
|
||||||
GetMessagesResponseObject instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'name': instance.name,
|
|
||||||
'date': instance.date,
|
|
||||||
'url': instance.url,
|
|
||||||
};
|
|
41
lib/app.dart
41
lib/app.dart
@ -5,6 +5,7 @@ import 'dart:developer';
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'state/app/modules/app_modules.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:badges/badges.dart' as badges;
|
import 'package:badges/badges.dart' as badges;
|
||||||
@ -21,10 +22,7 @@ import 'notification/notificationController.dart';
|
|||||||
import 'notification/notificationTasks.dart';
|
import 'notification/notificationTasks.dart';
|
||||||
import 'notification/notifyUpdater.dart';
|
import 'notification/notifyUpdater.dart';
|
||||||
import 'storage/base/settingsProvider.dart';
|
import 'storage/base/settingsProvider.dart';
|
||||||
import 'view/pages/files/files.dart';
|
|
||||||
import 'view/pages/overhang.dart';
|
import 'view/pages/overhang.dart';
|
||||||
import 'view/pages/talk/chatList.dart';
|
|
||||||
import 'view/pages/timetable/timetable.dart';
|
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
@ -99,26 +97,16 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
controller: Main.bottomNavigator,
|
controller: Main.bottomNavigator,
|
||||||
navBarOverlap: const NavBarOverlap.none(),
|
navBarOverlap: const NavBarOverlap.none(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
handleAndroidBackButtonPress: false,
|
||||||
|
|
||||||
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
|
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
|
||||||
tabs: [
|
tabs: [
|
||||||
PersistentTabConfig(
|
AppModule.getModule(Modules.timetable).toBottomTab(context),
|
||||||
screen: const Breaker(breaker: BreakerArea.timetable, child: Timetable()),
|
AppModule.getModule(Modules.talk).toBottomTab(
|
||||||
item: ItemConfig(
|
context,
|
||||||
activeForegroundColor: Theme.of(context).primaryColor,
|
itemBuilder: (icon) => Consumer<ChatListProps>(
|
||||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
|
||||||
icon: const Icon(Icons.calendar_month),
|
|
||||||
title: 'Vertretung'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PersistentTabConfig(
|
|
||||||
screen: const Breaker(breaker: BreakerArea.talk, child: ChatList()),
|
|
||||||
item: ItemConfig(
|
|
||||||
activeForegroundColor: Theme.of(context).primaryColor,
|
|
||||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
|
||||||
icon: Consumer<ChatListProps>(
|
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
if(value.primaryLoading()) return const Icon(Icons.chat);
|
if(value.primaryLoading()) return Icon(icon);
|
||||||
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
var messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
||||||
return badges.Badge(
|
return badges.Badge(
|
||||||
showBadge: messages > 0,
|
showBadge: messages > 0,
|
||||||
@ -130,22 +118,13 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
elevation: 1,
|
elevation: 1,
|
||||||
),
|
),
|
||||||
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||||
child: const Icon(Icons.chat),
|
child: Icon(icon),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: 'Talk',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PersistentTabConfig(
|
|
||||||
screen: const Breaker(breaker: BreakerArea.files, child: Files([])),
|
|
||||||
item: ItemConfig(
|
|
||||||
activeForegroundColor: Theme.of(context).primaryColor,
|
|
||||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
|
||||||
icon: const Icon(Icons.folder),
|
|
||||||
title: 'Dateien'
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
AppModule.getModule(Modules.files).toBottomTab(context),
|
||||||
|
|
||||||
PersistentTabConfig(
|
PersistentTabConfig(
|
||||||
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
|
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
|
||||||
item: ItemConfig(
|
item: ItemConfig(
|
||||||
|
@ -7,8 +7,10 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:loader_overlay/loader_overlay.dart';
|
import 'package:loader_overlay/loader_overlay.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
@ -24,7 +26,6 @@ import 'model/chatList/chatListProps.dart';
|
|||||||
import 'model/chatList/chatProps.dart';
|
import 'model/chatList/chatProps.dart';
|
||||||
import 'model/files/filesProps.dart';
|
import 'model/files/filesProps.dart';
|
||||||
import 'model/holidays/holidaysProps.dart';
|
import 'model/holidays/holidaysProps.dart';
|
||||||
import 'model/message/messageProps.dart';
|
|
||||||
import 'model/timetable/timetableProps.dart';
|
import 'model/timetable/timetableProps.dart';
|
||||||
import 'storage/base/settingsProvider.dart';
|
import 'storage/base/settingsProvider.dart';
|
||||||
import 'theming/darkAppTheme.dart';
|
import 'theming/darkAppTheme.dart';
|
||||||
@ -35,15 +36,20 @@ import 'widget/placeholderView.dart';
|
|||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
try {
|
var initialisationTasks = [
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform)
|
||||||
log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}");
|
.then((value) async => log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}"))
|
||||||
} catch (e) {
|
.onError((error, stackTrace) => log('Error initializing Firebase: $error')),
|
||||||
log('Error initializing Firebase app!');
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
|
PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem')
|
||||||
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
|
.then((certificate) => SecurityContext.defaultContext.setTrustedCertificatesBytes(certificate.buffer.asUint8List())),
|
||||||
|
|
||||||
|
Future(() async {
|
||||||
|
await HydratedStorage.build(storageDirectory: await getTemporaryDirectory()).then((storage) => HydratedBloc.storage = storage);
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
await Future.wait(initialisationTasks);
|
||||||
|
|
||||||
if(kReleaseMode) {
|
if(kReleaseMode) {
|
||||||
ErrorWidget.builder = (error) => PlaceholderView(
|
ErrorWidget.builder = (error) => PlaceholderView(
|
||||||
@ -65,7 +71,6 @@ Future<void> main() async {
|
|||||||
ChangeNotifierProvider(create: (context) => ChatProps()),
|
ChangeNotifierProvider(create: (context) => ChatProps()),
|
||||||
ChangeNotifierProvider(create: (context) => FilesProps()),
|
ChangeNotifierProvider(create: (context) => FilesProps()),
|
||||||
|
|
||||||
ChangeNotifierProvider(create: (context) => MessageProps()),
|
|
||||||
ChangeNotifierProvider(create: (context) => HolidaysProps()),
|
ChangeNotifierProvider(create: (context) => HolidaysProps()),
|
||||||
],
|
],
|
||||||
child: const Main(),
|
child: const Main(),
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
import '../../api/apiResponse.dart';
|
|
||||||
import '../../api/mhsl/message/getMessages/getMessagesCache.dart';
|
|
||||||
import '../../api/mhsl/message/getMessages/getMessagesResponse.dart';
|
|
||||||
import '../dataHolder.dart';
|
|
||||||
|
|
||||||
class MessageProps extends DataHolder {
|
|
||||||
GetMessagesResponse? _getMessagesResponse;
|
|
||||||
GetMessagesResponse get getMessagesResponse => _getMessagesResponse!;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<ApiResponse?> properties() => [_getMessagesResponse];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void run({renew}) {
|
|
||||||
GetMessagesCache(
|
|
||||||
renew: renew,
|
|
||||||
onUpdate: (GetMessagesResponse data) => {
|
|
||||||
_getMessagesResponse = data,
|
|
||||||
notifyListeners(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
42
lib/state/app/infrastructure/dataLoader/data_loader.dart
Normal file
42
lib/state/app/infrastructure/dataLoader/data_loader.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
abstract class DataLoader<TResult> {
|
||||||
|
final Dio dio;
|
||||||
|
DataLoader(this.dio) {
|
||||||
|
dio.options.connectTimeout = const Duration(seconds: 10).inMilliseconds;
|
||||||
|
dio.options.sendTimeout = const Duration(seconds: 30).inMilliseconds;
|
||||||
|
dio.options.receiveTimeout = const Duration(seconds: 30).inMilliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TResult> run() async {
|
||||||
|
var fetcher = fetch();
|
||||||
|
await Future.wait([
|
||||||
|
fetcher,
|
||||||
|
Future.delayed(const Duration(milliseconds: 500)) // TODO tune or remove
|
||||||
|
]);
|
||||||
|
|
||||||
|
var response = await fetcher;
|
||||||
|
try {
|
||||||
|
return assemble(DataLoaderResult(
|
||||||
|
json: jsonDecode(response.data!),
|
||||||
|
headers: response.headers.map.map((key, value) => MapEntry(key, value.join(';'))),
|
||||||
|
));
|
||||||
|
} catch(trace, e) {
|
||||||
|
log(trace.toString());
|
||||||
|
throw(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response<String>> fetch();
|
||||||
|
TResult assemble(DataLoaderResult data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataLoaderResult {
|
||||||
|
final Map<String, dynamic> json;
|
||||||
|
final Map<String, String> headers;
|
||||||
|
|
||||||
|
DataLoaderResult({required this.json, required this.headers});
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import 'data_loader.dart';
|
||||||
|
|
||||||
|
abstract class MhslDataLoader<TResult> extends DataLoader<TResult> {
|
||||||
|
MhslDataLoader() : super(Dio(BaseOptions(
|
||||||
|
baseUrl: 'https://mhsl.eu/marianum/marianummobile/'
|
||||||
|
)));
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:jiffy/jiffy.dart';
|
||||||
|
|
||||||
|
import 'loadable_state_event.dart';
|
||||||
|
import 'loadable_state_state.dart';
|
||||||
|
|
||||||
|
class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
|
||||||
|
late StreamSubscription<List<ConnectivityResult>> _updateStream;
|
||||||
|
void Function()? reFetch;
|
||||||
|
|
||||||
|
LoadableStateBloc() : super(const LoadableStateState(connections: null)) {
|
||||||
|
on<ConnectivityChanged>((event, emit) {
|
||||||
|
emit(event.state);
|
||||||
|
if(connectivityStatusKnown() && isConnected()) {
|
||||||
|
if(reFetch == null) return;
|
||||||
|
reFetch!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emitConnectivity(List<ConnectivityResult> result) => add(ConnectivityChanged(LoadableStateState(connections: result)));
|
||||||
|
|
||||||
|
Connectivity().checkConnectivity().then(emitConnectivity);
|
||||||
|
_updateStream = Connectivity().onConnectivityChanged.listen(emitConnectivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectivityStatusKnown() => state.connections != null;
|
||||||
|
bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true);
|
||||||
|
bool allowRetry() => reFetch != null;
|
||||||
|
|
||||||
|
IconData connectionIcon() => connectivityStatusKnown()
|
||||||
|
? isConnected()
|
||||||
|
? Icons.nearby_error
|
||||||
|
: Icons.signal_wifi_connected_no_internet_4
|
||||||
|
: Icons.device_unknown;
|
||||||
|
|
||||||
|
Color connectionColor(BuildContext context) => connectivityStatusKnown() && !isConnected()
|
||||||
|
? Colors.grey.shade600
|
||||||
|
: Theme.of(context).primaryColor;
|
||||||
|
|
||||||
|
String connectionText({int? lastUpdated}) => connectivityStatusKnown()
|
||||||
|
? isConnected()
|
||||||
|
? 'Verbindung fehlgeschlagen'
|
||||||
|
: 'Offline${lastUpdated == null ? '' : ' - Stand von ${Jiffy.parseFromMillisecondsSinceEpoch(lastUpdated).fromNow()}'}'
|
||||||
|
: 'Unbekannte Fehlerursache';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_updateStream.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import 'loadable_state_state.dart';
|
||||||
|
|
||||||
|
sealed class LoadableStateEvent {}
|
||||||
|
final class ConnectivityChanged extends LoadableStateEvent {
|
||||||
|
final LoadableStateState state;
|
||||||
|
ConnectivityChanged(this.state);
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'loadable_state_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class LoadableStateState with _$LoadableStateState {
|
||||||
|
const factory LoadableStateState({
|
||||||
|
required List<ConnectivityResult>? connections,
|
||||||
|
}) = _LoadableStateState;
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
// 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 'loadable_state_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 _$LoadableStateState {
|
||||||
|
List<ConnectivityResult>? get connections =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$LoadableStateStateCopyWith<LoadableStateState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $LoadableStateStateCopyWith<$Res> {
|
||||||
|
factory $LoadableStateStateCopyWith(
|
||||||
|
LoadableStateState value, $Res Function(LoadableStateState) then) =
|
||||||
|
_$LoadableStateStateCopyWithImpl<$Res, LoadableStateState>;
|
||||||
|
@useResult
|
||||||
|
$Res call({List<ConnectivityResult>? connections});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$LoadableStateStateCopyWithImpl<$Res, $Val extends LoadableStateState>
|
||||||
|
implements $LoadableStateStateCopyWith<$Res> {
|
||||||
|
_$LoadableStateStateCopyWithImpl(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? connections = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
connections: freezed == connections
|
||||||
|
? _value.connections
|
||||||
|
: connections // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ConnectivityResult>?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LoadableStateStateImplCopyWith<$Res>
|
||||||
|
implements $LoadableStateStateCopyWith<$Res> {
|
||||||
|
factory _$$LoadableStateStateImplCopyWith(_$LoadableStateStateImpl value,
|
||||||
|
$Res Function(_$LoadableStateStateImpl) then) =
|
||||||
|
__$$LoadableStateStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({List<ConnectivityResult>? connections});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LoadableStateStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$LoadableStateStateCopyWithImpl<$Res, _$LoadableStateStateImpl>
|
||||||
|
implements _$$LoadableStateStateImplCopyWith<$Res> {
|
||||||
|
__$$LoadableStateStateImplCopyWithImpl(_$LoadableStateStateImpl _value,
|
||||||
|
$Res Function(_$LoadableStateStateImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? connections = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$LoadableStateStateImpl(
|
||||||
|
connections: freezed == connections
|
||||||
|
? _value._connections
|
||||||
|
: connections // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<ConnectivityResult>?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$LoadableStateStateImpl implements _LoadableStateState {
|
||||||
|
const _$LoadableStateStateImpl(
|
||||||
|
{required final List<ConnectivityResult>? connections})
|
||||||
|
: _connections = connections;
|
||||||
|
|
||||||
|
final List<ConnectivityResult>? _connections;
|
||||||
|
@override
|
||||||
|
List<ConnectivityResult>? get connections {
|
||||||
|
final value = _connections;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_connections is EqualUnmodifiableListView) return _connections;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadableStateState(connections: $connections)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$LoadableStateStateImpl &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._connections, _connections));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, const DeepCollectionEquality().hash(_connections));
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith =>
|
||||||
|
__$$LoadableStateStateImplCopyWithImpl<_$LoadableStateStateImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _LoadableStateState implements LoadableStateState {
|
||||||
|
const factory _LoadableStateState(
|
||||||
|
{required final List<ConnectivityResult>? connections}) =
|
||||||
|
_$LoadableStateStateImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ConnectivityResult>? get connections;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$LoadableStateStateImplCopyWith<_$LoadableStateStateImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'loading_error.dart';
|
||||||
|
|
||||||
|
part 'loadable_state.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class LoadableState<TState> with _$LoadableState {
|
||||||
|
const LoadableState._();
|
||||||
|
|
||||||
|
const factory LoadableState({
|
||||||
|
@Default(true) bool isLoading,
|
||||||
|
@Default(null) TState? data,
|
||||||
|
@Default(null) int? lastFetch,
|
||||||
|
@Default(null) void Function()? reFetch,
|
||||||
|
@Default(null) LoadingError? error,
|
||||||
|
}) = _LoadableState;
|
||||||
|
|
||||||
|
bool _hasError() => error != null;
|
||||||
|
bool _hasData() => data != null;
|
||||||
|
|
||||||
|
bool showPrimaryLoading() => isLoading && !_hasData();
|
||||||
|
bool showBackgroundLoading() => isLoading && _hasData();
|
||||||
|
bool showErrorBar() => _hasError() && _hasData();
|
||||||
|
bool showError() => _hasError() && !_hasData();
|
||||||
|
bool showContent() => _hasData();
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
// 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 'loadable_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 _$LoadableState<TState> {
|
||||||
|
bool get isLoading => throw _privateConstructorUsedError;
|
||||||
|
TState? get data => throw _privateConstructorUsedError;
|
||||||
|
int? get lastFetch => throw _privateConstructorUsedError;
|
||||||
|
void Function()? get reFetch => throw _privateConstructorUsedError;
|
||||||
|
LoadingError? get error => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$LoadableStateCopyWith<TState, LoadableState<TState>> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $LoadableStateCopyWith<TState, $Res> {
|
||||||
|
factory $LoadableStateCopyWith(LoadableState<TState> value,
|
||||||
|
$Res Function(LoadableState<TState>) then) =
|
||||||
|
_$LoadableStateCopyWithImpl<TState, $Res, LoadableState<TState>>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{bool isLoading,
|
||||||
|
TState? data,
|
||||||
|
int? lastFetch,
|
||||||
|
void Function()? reFetch,
|
||||||
|
LoadingError? error});
|
||||||
|
|
||||||
|
$LoadingErrorCopyWith<$Res>? get error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$LoadableStateCopyWithImpl<TState, $Res,
|
||||||
|
$Val extends LoadableState<TState>>
|
||||||
|
implements $LoadableStateCopyWith<TState, $Res> {
|
||||||
|
_$LoadableStateCopyWithImpl(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? isLoading = null,
|
||||||
|
Object? data = freezed,
|
||||||
|
Object? lastFetch = freezed,
|
||||||
|
Object? reFetch = freezed,
|
||||||
|
Object? error = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
isLoading: null == isLoading
|
||||||
|
? _value.isLoading
|
||||||
|
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
data: freezed == data
|
||||||
|
? _value.data
|
||||||
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TState?,
|
||||||
|
lastFetch: freezed == lastFetch
|
||||||
|
? _value.lastFetch
|
||||||
|
: lastFetch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
reFetch: freezed == reFetch
|
||||||
|
? _value.reFetch
|
||||||
|
: reFetch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as void Function()?,
|
||||||
|
error: freezed == error
|
||||||
|
? _value.error
|
||||||
|
: error // ignore: cast_nullable_to_non_nullable
|
||||||
|
as LoadingError?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$LoadingErrorCopyWith<$Res>? get error {
|
||||||
|
if (_value.error == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $LoadingErrorCopyWith<$Res>(_value.error!, (value) {
|
||||||
|
return _then(_value.copyWith(error: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LoadableStateImplCopyWith<TState, $Res>
|
||||||
|
implements $LoadableStateCopyWith<TState, $Res> {
|
||||||
|
factory _$$LoadableStateImplCopyWith(_$LoadableStateImpl<TState> value,
|
||||||
|
$Res Function(_$LoadableStateImpl<TState>) then) =
|
||||||
|
__$$LoadableStateImplCopyWithImpl<TState, $Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{bool isLoading,
|
||||||
|
TState? data,
|
||||||
|
int? lastFetch,
|
||||||
|
void Function()? reFetch,
|
||||||
|
LoadingError? error});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$LoadingErrorCopyWith<$Res>? get error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||||
|
extends _$LoadableStateCopyWithImpl<TState, $Res,
|
||||||
|
_$LoadableStateImpl<TState>>
|
||||||
|
implements _$$LoadableStateImplCopyWith<TState, $Res> {
|
||||||
|
__$$LoadableStateImplCopyWithImpl(_$LoadableStateImpl<TState> _value,
|
||||||
|
$Res Function(_$LoadableStateImpl<TState>) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? isLoading = null,
|
||||||
|
Object? data = freezed,
|
||||||
|
Object? lastFetch = freezed,
|
||||||
|
Object? reFetch = freezed,
|
||||||
|
Object? error = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$LoadableStateImpl<TState>(
|
||||||
|
isLoading: null == isLoading
|
||||||
|
? _value.isLoading
|
||||||
|
: isLoading // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
data: freezed == data
|
||||||
|
? _value.data
|
||||||
|
: data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as TState?,
|
||||||
|
lastFetch: freezed == lastFetch
|
||||||
|
? _value.lastFetch
|
||||||
|
: lastFetch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
reFetch: freezed == reFetch
|
||||||
|
? _value.reFetch
|
||||||
|
: reFetch // ignore: cast_nullable_to_non_nullable
|
||||||
|
as void Function()?,
|
||||||
|
error: freezed == error
|
||||||
|
? _value.error
|
||||||
|
: error // ignore: cast_nullable_to_non_nullable
|
||||||
|
as LoadingError?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||||
|
const _$LoadableStateImpl(
|
||||||
|
{this.isLoading = true,
|
||||||
|
this.data = null,
|
||||||
|
this.lastFetch = null,
|
||||||
|
this.reFetch = null,
|
||||||
|
this.error = null})
|
||||||
|
: super._();
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool isLoading;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final TState? data;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int? lastFetch;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final void Function()? reFetch;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final LoadingError? error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, lastFetch: $lastFetch, reFetch: $reFetch, error: $error)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$LoadableStateImpl<TState> &&
|
||||||
|
(identical(other.isLoading, isLoading) ||
|
||||||
|
other.isLoading == isLoading) &&
|
||||||
|
const DeepCollectionEquality().equals(other.data, data) &&
|
||||||
|
(identical(other.lastFetch, lastFetch) ||
|
||||||
|
other.lastFetch == lastFetch) &&
|
||||||
|
(identical(other.reFetch, reFetch) || other.reFetch == reFetch) &&
|
||||||
|
(identical(other.error, error) || other.error == error));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, isLoading,
|
||||||
|
const DeepCollectionEquality().hash(data), lastFetch, reFetch, error);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>>
|
||||||
|
get copyWith => __$$LoadableStateImplCopyWithImpl<TState,
|
||||||
|
_$LoadableStateImpl<TState>>(this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _LoadableState<TState> extends LoadableState<TState> {
|
||||||
|
const factory _LoadableState(
|
||||||
|
{final bool isLoading,
|
||||||
|
final TState? data,
|
||||||
|
final int? lastFetch,
|
||||||
|
final void Function()? reFetch,
|
||||||
|
final LoadingError? error}) = _$LoadableStateImpl<TState>;
|
||||||
|
const _LoadableState._() : super._();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isLoading;
|
||||||
|
@override
|
||||||
|
TState? get data;
|
||||||
|
@override
|
||||||
|
int? get lastFetch;
|
||||||
|
@override
|
||||||
|
void Function()? get reFetch;
|
||||||
|
@override
|
||||||
|
LoadingError? get error;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$LoadableStateImplCopyWith<TState, _$LoadableStateImpl<TState>>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'loading_error.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class LoadingError with _$LoadingError {
|
||||||
|
const factory LoadingError({
|
||||||
|
required String message,
|
||||||
|
@Default(false) bool allowRetry,
|
||||||
|
}) = _LoadingError;
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
// 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 'loading_error.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 _$LoadingError {
|
||||||
|
String get message => throw _privateConstructorUsedError;
|
||||||
|
bool get allowRetry => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$LoadingErrorCopyWith<LoadingError> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $LoadingErrorCopyWith<$Res> {
|
||||||
|
factory $LoadingErrorCopyWith(
|
||||||
|
LoadingError value, $Res Function(LoadingError) then) =
|
||||||
|
_$LoadingErrorCopyWithImpl<$Res, LoadingError>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String message, bool allowRetry});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$LoadingErrorCopyWithImpl<$Res, $Val extends LoadingError>
|
||||||
|
implements $LoadingErrorCopyWith<$Res> {
|
||||||
|
_$LoadingErrorCopyWithImpl(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? message = null,
|
||||||
|
Object? allowRetry = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
message: null == message
|
||||||
|
? _value.message
|
||||||
|
: message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
allowRetry: null == allowRetry
|
||||||
|
? _value.allowRetry
|
||||||
|
: allowRetry // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LoadingErrorImplCopyWith<$Res>
|
||||||
|
implements $LoadingErrorCopyWith<$Res> {
|
||||||
|
factory _$$LoadingErrorImplCopyWith(
|
||||||
|
_$LoadingErrorImpl value, $Res Function(_$LoadingErrorImpl) then) =
|
||||||
|
__$$LoadingErrorImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String message, bool allowRetry});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LoadingErrorImplCopyWithImpl<$Res>
|
||||||
|
extends _$LoadingErrorCopyWithImpl<$Res, _$LoadingErrorImpl>
|
||||||
|
implements _$$LoadingErrorImplCopyWith<$Res> {
|
||||||
|
__$$LoadingErrorImplCopyWithImpl(
|
||||||
|
_$LoadingErrorImpl _value, $Res Function(_$LoadingErrorImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? message = null,
|
||||||
|
Object? allowRetry = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$LoadingErrorImpl(
|
||||||
|
message: null == message
|
||||||
|
? _value.message
|
||||||
|
: message // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
allowRetry: null == allowRetry
|
||||||
|
? _value.allowRetry
|
||||||
|
: allowRetry // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$LoadingErrorImpl implements _LoadingError {
|
||||||
|
const _$LoadingErrorImpl({required this.message, this.allowRetry = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String message;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool allowRetry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadingError(message: $message, allowRetry: $allowRetry)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$LoadingErrorImpl &&
|
||||||
|
(identical(other.message, message) || other.message == message) &&
|
||||||
|
(identical(other.allowRetry, allowRetry) ||
|
||||||
|
other.allowRetry == allowRetry));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, message, allowRetry);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
|
||||||
|
__$$LoadingErrorImplCopyWithImpl<_$LoadingErrorImpl>(this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _LoadingError implements LoadingError {
|
||||||
|
const factory _LoadingError(
|
||||||
|
{required final String message,
|
||||||
|
final bool allowRetry}) = _$LoadingErrorImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get message;
|
||||||
|
@override
|
||||||
|
bool get allowRetry;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$LoadingErrorImplCopyWith<_$LoadingErrorImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'loadable_state_consumer.dart';
|
||||||
|
|
||||||
|
class LoadableStateBackgroundLoading extends StatelessWidget {
|
||||||
|
final bool visible;
|
||||||
|
const LoadableStateBackgroundLoading({required this.visible, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => AnimatedSwitcher(
|
||||||
|
duration: LoadableStateConsumer.animationDuration,
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) => SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0.0, -1.0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(animation),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: visible ? const LinearProgressIndicator() : const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../../../../widget/conditional_wrapper.dart';
|
||||||
|
import '../../utilityWidgets/bloc_module.dart';
|
||||||
|
import '../../utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
|
||||||
|
import '../bloc/loadable_state_bloc.dart';
|
||||||
|
import '../bloc/loadable_state_state.dart';
|
||||||
|
import '../loadable_state.dart';
|
||||||
|
import 'loadable_state_background_loading.dart';
|
||||||
|
import 'loadable_state_error_bar.dart';
|
||||||
|
import 'loadable_state_error_screen.dart';
|
||||||
|
import 'loadable_state_primary_loading.dart';
|
||||||
|
|
||||||
|
class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<TState>, LoadableState<TState>>, TState> extends StatelessWidget {
|
||||||
|
final Widget Function(TState state, bool loading) child;
|
||||||
|
final bool wrapWithScrollView;
|
||||||
|
const LoadableStateConsumer({required this.child, this.wrapWithScrollView = false, super.key});
|
||||||
|
|
||||||
|
static Duration animationDuration = const Duration(milliseconds: 200);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var loadableState = context.watch<TController>().state;
|
||||||
|
var childWidget = ConditionalWrapper(
|
||||||
|
condition: loadableState.reFetch != null,
|
||||||
|
wrapper: (child) => RefreshIndicator(
|
||||||
|
onRefresh: () {
|
||||||
|
if(loadableState.reFetch != null) loadableState.reFetch!();
|
||||||
|
return Future.value();
|
||||||
|
},
|
||||||
|
child: ConditionalWrapper(
|
||||||
|
condition: wrapWithScrollView,
|
||||||
|
wrapper: (child) => SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
child: child
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: loadableState.showContent()
|
||||||
|
? child(loadableState.data, loadableState.isLoading)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return BlocModule<LoadableStateBloc, LoadableStateState>(
|
||||||
|
create: (context) => LoadableStateBloc(),
|
||||||
|
child: (context, bloc, state) {
|
||||||
|
bloc.reFetch = loadableState.reFetch;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
LoadableStateErrorBar(visible: loadableState.showErrorBar(), message: loadableState.error?.message, lastUpdated: loadableState.lastFetch),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
LoadableStatePrimaryLoading(visible: loadableState.showPrimaryLoading()),
|
||||||
|
LoadableStateBackgroundLoading(visible: loadableState.showBackgroundLoading()),
|
||||||
|
LoadableStateErrorScreen(visible: loadableState.showError(), message: loadableState.error?.message),
|
||||||
|
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: loadableState.showContent() ? 1.0 : 0.0,
|
||||||
|
duration: animationDuration,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: childWidget,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../../widget/infoDialog.dart';
|
||||||
|
import '../bloc/loadable_state_bloc.dart';
|
||||||
|
|
||||||
|
class LoadableStateErrorBar extends StatelessWidget {
|
||||||
|
final bool visible;
|
||||||
|
final String? message;
|
||||||
|
final int? lastUpdated;
|
||||||
|
const LoadableStateErrorBar({required this.visible, this.message, this.lastUpdated, super.key});
|
||||||
|
|
||||||
|
final Duration animationDuration = const Duration(milliseconds: 200);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => AnimatedSize(
|
||||||
|
duration: animationDuration,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: animationDuration,
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) => SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0.0, -1.0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(animation),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Visibility(
|
||||||
|
key: Key(visible.hashCode.toString()),
|
||||||
|
visible: visible,
|
||||||
|
replacement: const SizedBox(width: double.infinity),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
var bloc = context.watch<LoadableStateBloc>();
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
if(!bloc.isConnected()) return;
|
||||||
|
InfoDialog.show(context, 'Exception: ${message.toString()}');
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bloc.connectionColor(context),
|
||||||
|
),
|
||||||
|
child: LoadableStateErrorBarText(lastUpdated: lastUpdated),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadableStateErrorBarText extends StatefulWidget {
|
||||||
|
final int? lastUpdated;
|
||||||
|
const LoadableStateErrorBarText({required this.lastUpdated, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoadableStateErrorBarText> createState() => _LoadableStateErrorBarTextState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadableStateErrorBarTextState extends State<LoadableStateErrorBarText> {
|
||||||
|
late Timer _rebuildTimer;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_rebuildTimer = Timer.periodic(const Duration(seconds: 10), (timer) => setState(() {}));
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var bloc = context.watch<LoadableStateBloc>();
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(bloc.connectionIcon(), size: 14),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(bloc.connectionText(lastUpdated: widget.lastUpdated), style: const TextStyle(fontSize: 12))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_rebuildTimer.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../bloc/loadable_state_bloc.dart';
|
||||||
|
import 'loadable_state_consumer.dart';
|
||||||
|
|
||||||
|
class LoadableStateErrorScreen extends StatelessWidget {
|
||||||
|
final bool visible;
|
||||||
|
final String? message;
|
||||||
|
const LoadableStateErrorScreen({required this.visible, this.message, super.key});
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bloc = context.watch<LoadableStateBloc>();
|
||||||
|
return AnimatedOpacity(
|
||||||
|
opacity: visible ? 1.0 : 0.0,
|
||||||
|
duration: LoadableStateConsumer.animationDuration,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: !visible ? null : Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(bloc.connectionIcon(), size: 40),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(bloc.connectionText(), style: const TextStyle(fontSize: 20)),
|
||||||
|
|
||||||
|
if(bloc.allowRetry()) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextButton(onPressed: () => bloc.reFetch!(), child: const Text('Erneut versuschen')),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
message ?? 'Task failed successfully :)',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
fontSize: 12
|
||||||
|
),
|
||||||
|
maxLines: 10,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'loadable_state_consumer.dart';
|
||||||
|
|
||||||
|
class LoadableStatePrimaryLoading extends StatelessWidget {
|
||||||
|
final bool visible;
|
||||||
|
const LoadableStatePrimaryLoading({required this.visible, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => AnimatedOpacity(
|
||||||
|
opacity: visible ? 1.0 : 0.0,
|
||||||
|
duration: LoadableStateConsumer.animationDuration,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
1
lib/state/app/infrastructure/repository/repository.dart
Normal file
1
lib/state/app/infrastructure/repository/repository.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
abstract class Repository<TState> {}
|
22
lib/state/app/infrastructure/utilityWidgets/bloc_module.dart
Normal file
22
lib/state/app/infrastructure/utilityWidgets/bloc_module.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class BlocModule<TBloc extends StateStreamableSource<TState>, TState> extends StatelessWidget {
|
||||||
|
final TBloc Function(BuildContext context) create;
|
||||||
|
final Widget Function(BuildContext context, TBloc bloc, TState state) child;
|
||||||
|
final bool autoRebuild;
|
||||||
|
const BlocModule({required this.create, required this.child, this.autoRebuild = false, super.key});
|
||||||
|
|
||||||
|
Widget rebuildChild(BuildContext context) => child(context, context.watch<TBloc>(), context.watch<TBloc>().state);
|
||||||
|
Widget staticChild(BuildContext context) => child(context, context.read<TBloc>(), context.read<TBloc>().state);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => BlocProvider<TBloc>(
|
||||||
|
create: create,
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => autoRebuild
|
||||||
|
? rebuildChild(context)
|
||||||
|
: staticChild(context)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
|
|
||||||
|
import '../../loadableState/loading_error.dart';
|
||||||
|
import '../../repository/repository.dart';
|
||||||
|
import 'loadable_hydrated_bloc_event.dart';
|
||||||
|
import '../../loadableState/loadable_state.dart';
|
||||||
|
import 'loadable_save_context.dart';
|
||||||
|
|
||||||
|
abstract class LoadableHydratedBloc<
|
||||||
|
TEvent extends LoadableHydratedBlocEvent<TState>,
|
||||||
|
TState,
|
||||||
|
TRepository extends Repository<TState>
|
||||||
|
> extends HydratedBloc<
|
||||||
|
LoadableHydratedBlocEvent<TState>,
|
||||||
|
LoadableState<TState>
|
||||||
|
> {
|
||||||
|
late TRepository _repository;
|
||||||
|
LoadableHydratedBloc() : super(const LoadableState()) {
|
||||||
|
|
||||||
|
on<Emit<TState>>((event, emit) => emit(LoadableState(
|
||||||
|
isLoading: event.loading,
|
||||||
|
data: event.state(innerState ?? fromNothing()),
|
||||||
|
lastFetch: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
reFetch: retry
|
||||||
|
)));
|
||||||
|
|
||||||
|
on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(
|
||||||
|
isLoading: true,
|
||||||
|
data: innerState,
|
||||||
|
lastFetch: state.lastFetch
|
||||||
|
)));
|
||||||
|
|
||||||
|
on<ClearState<TState>>((event, emit) => emit(const LoadableState()));
|
||||||
|
|
||||||
|
on<Error<TState>>((event, emit) => emit(LoadableState(
|
||||||
|
isLoading: false,
|
||||||
|
data: innerState,
|
||||||
|
lastFetch: state.lastFetch,
|
||||||
|
reFetch: retry,
|
||||||
|
error: event.error
|
||||||
|
)));
|
||||||
|
|
||||||
|
_repository = repository();
|
||||||
|
fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
TState? get innerState => state.data;
|
||||||
|
TRepository get repo => _repository;
|
||||||
|
|
||||||
|
void retry() {
|
||||||
|
log('Fetch retry triggered for ${TState.toString()}');
|
||||||
|
add(RefetchStarted<TState>());
|
||||||
|
fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetch() {
|
||||||
|
log('Fetching data for ${TState.toString()}');
|
||||||
|
gatherData().catchError(
|
||||||
|
(e) {
|
||||||
|
log('Error while fetching ${TState.toString()}: ${e.toString()}');
|
||||||
|
add(Error(LoadingError(
|
||||||
|
message: e.message,
|
||||||
|
allowRetry: true,
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
).then((value) {
|
||||||
|
log('Fetch for ${TState.toString()} completed!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
fromJson(Map<String, dynamic> json) {
|
||||||
|
var rawData = LoadableSaveContext.unwrap(json);
|
||||||
|
return LoadableState(isLoading: true, lastFetch: rawData.meta.timestamp, data: fromStorage(rawData.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic>? toJson(LoadableState<TState> state) => LoadableSaveContext.wrap(
|
||||||
|
toStorage(state.data),
|
||||||
|
state.lastFetch ?? DateTime.now().millisecondsSinceEpoch
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> gatherData();
|
||||||
|
TRepository repository();
|
||||||
|
|
||||||
|
TState fromNothing();
|
||||||
|
TState fromStorage(Map<String, dynamic> json);
|
||||||
|
Map<String, dynamic>? toStorage(TState state);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import '../../loadableState/loading_error.dart';
|
||||||
|
|
||||||
|
class LoadableHydratedBlocEvent<TState> {}
|
||||||
|
class Emit<TState> extends LoadableHydratedBlocEvent<TState> {
|
||||||
|
final TState Function(TState state) state;
|
||||||
|
final bool loading;
|
||||||
|
Emit(this.state, {this.loading = false});
|
||||||
|
}
|
||||||
|
class ClearState<TState> extends LoadableHydratedBlocEvent<TState> {}
|
||||||
|
class Error<TState> extends LoadableHydratedBlocEvent<TState> {
|
||||||
|
final LoadingError error;
|
||||||
|
Error(this.error);
|
||||||
|
}
|
||||||
|
class RefetchStarted<TState> extends LoadableHydratedBlocEvent<TState> {}
|
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'loadable_save_context.freezed.dart';
|
||||||
|
part 'loadable_save_context.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class LoadableSaveContext with _$LoadableSaveContext {
|
||||||
|
const LoadableSaveContext._();
|
||||||
|
const factory LoadableSaveContext({
|
||||||
|
required int timestamp,
|
||||||
|
}) = _LoadableSaveContext;
|
||||||
|
|
||||||
|
factory LoadableSaveContext.fromJson(Map<String, dynamic> json) => _$LoadableSaveContextFromJson(json);
|
||||||
|
|
||||||
|
static String dataKey = 'data';
|
||||||
|
static String metaKey = 'meta';
|
||||||
|
|
||||||
|
static Map<String, dynamic> wrap(Map<String, dynamic>? data, int lastFetch) =>
|
||||||
|
{dataKey: data, metaKey: LoadableSaveContext(timestamp: lastFetch).toJson()};
|
||||||
|
|
||||||
|
static ({Map<String, dynamic> data, LoadableSaveContext meta}) unwrap(Map<String, dynamic> data) =>
|
||||||
|
(data: data[dataKey] as Map<String, dynamic>, meta: LoadableSaveContext.fromJson(data[metaKey]));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,155 @@
|
|||||||
|
// 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 'loadable_save_context.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');
|
||||||
|
|
||||||
|
LoadableSaveContext _$LoadableSaveContextFromJson(Map<String, dynamic> json) {
|
||||||
|
return _LoadableSaveContext.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$LoadableSaveContext {
|
||||||
|
int get timestamp => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$LoadableSaveContextCopyWith<LoadableSaveContext> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $LoadableSaveContextCopyWith<$Res> {
|
||||||
|
factory $LoadableSaveContextCopyWith(
|
||||||
|
LoadableSaveContext value, $Res Function(LoadableSaveContext) then) =
|
||||||
|
_$LoadableSaveContextCopyWithImpl<$Res, LoadableSaveContext>;
|
||||||
|
@useResult
|
||||||
|
$Res call({int timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$LoadableSaveContextCopyWithImpl<$Res, $Val extends LoadableSaveContext>
|
||||||
|
implements $LoadableSaveContextCopyWith<$Res> {
|
||||||
|
_$LoadableSaveContextCopyWithImpl(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? timestamp = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
timestamp: null == timestamp
|
||||||
|
? _value.timestamp
|
||||||
|
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$LoadableSaveContextImplCopyWith<$Res>
|
||||||
|
implements $LoadableSaveContextCopyWith<$Res> {
|
||||||
|
factory _$$LoadableSaveContextImplCopyWith(_$LoadableSaveContextImpl value,
|
||||||
|
$Res Function(_$LoadableSaveContextImpl) then) =
|
||||||
|
__$$LoadableSaveContextImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({int timestamp});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$LoadableSaveContextImplCopyWithImpl<$Res>
|
||||||
|
extends _$LoadableSaveContextCopyWithImpl<$Res, _$LoadableSaveContextImpl>
|
||||||
|
implements _$$LoadableSaveContextImplCopyWith<$Res> {
|
||||||
|
__$$LoadableSaveContextImplCopyWithImpl(_$LoadableSaveContextImpl _value,
|
||||||
|
$Res Function(_$LoadableSaveContextImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? timestamp = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$LoadableSaveContextImpl(
|
||||||
|
timestamp: null == timestamp
|
||||||
|
? _value.timestamp
|
||||||
|
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$LoadableSaveContextImpl extends _LoadableSaveContext {
|
||||||
|
const _$LoadableSaveContextImpl({required this.timestamp}) : super._();
|
||||||
|
|
||||||
|
factory _$LoadableSaveContextImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$LoadableSaveContextImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int timestamp;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadableSaveContext(timestamp: $timestamp)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$LoadableSaveContextImpl &&
|
||||||
|
(identical(other.timestamp, timestamp) ||
|
||||||
|
other.timestamp == timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, timestamp);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith =>
|
||||||
|
__$$LoadableSaveContextImplCopyWithImpl<_$LoadableSaveContextImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$LoadableSaveContextImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _LoadableSaveContext extends LoadableSaveContext {
|
||||||
|
const factory _LoadableSaveContext({required final int timestamp}) =
|
||||||
|
_$LoadableSaveContextImpl;
|
||||||
|
const _LoadableSaveContext._() : super._();
|
||||||
|
|
||||||
|
factory _LoadableSaveContext.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$LoadableSaveContextImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get timestamp;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$LoadableSaveContextImplCopyWith<_$LoadableSaveContextImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'loadable_save_context.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$LoadableSaveContextImpl _$$LoadableSaveContextImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$LoadableSaveContextImpl(
|
||||||
|
timestamp: json['timestamp'] as int,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$LoadableSaveContextImplToJson(
|
||||||
|
_$LoadableSaveContextImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'timestamp': instance.timestamp,
|
||||||
|
};
|
60
lib/state/app/modules/app_modules.dart
Normal file
60
lib/state/app/modules/app_modules.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
|
import '../../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
|
import '../../../model/breakers/Breaker.dart';
|
||||||
|
import '../../../view/pages/files/files.dart';
|
||||||
|
import '../../../view/pages/more/holidays/holidays.dart';
|
||||||
|
import '../../../view/pages/more/roomplan/roomplan.dart';
|
||||||
|
import '../../../view/pages/talk/chatList.dart';
|
||||||
|
import '../../../view/pages/timetable/timetable.dart';
|
||||||
|
import '../../../widget/centeredLeading.dart';
|
||||||
|
import 'gradeAverages/view/grade_averages_view.dart';
|
||||||
|
import 'marianumMessage/view/marianum_message_list_view.dart';
|
||||||
|
|
||||||
|
class AppModule {
|
||||||
|
String name;
|
||||||
|
IconData icon;
|
||||||
|
Widget Function() create;
|
||||||
|
|
||||||
|
AppModule(this.name, this.icon, this.create);
|
||||||
|
|
||||||
|
static Map<Modules, AppModule> modules() => {
|
||||||
|
Modules.timetable: AppModule('Vertretung', Icons.calendar_month, Timetable.new),
|
||||||
|
Modules.talk: AppModule('Talk', Icons.chat, ChatList.new),
|
||||||
|
Modules.files: AppModule('Files', Icons.folder, Files.new),
|
||||||
|
Modules.marianumMessage: AppModule('Marianum Message', Icons.newspaper, MarianumMessageListView.new),
|
||||||
|
Modules.roomPlan: AppModule('Raumplan', Icons.location_pin, Roomplan.new),
|
||||||
|
Modules.gradeAveragesCalculator: AppModule('Notendurschnittsrechner', Icons.calculate, GradeAveragesView.new),
|
||||||
|
Modules.holidays: AppModule('Schulferien', Icons.holiday_village, Holidays.new),
|
||||||
|
};
|
||||||
|
|
||||||
|
static AppModule getModule(Modules module) => modules()[module]!;
|
||||||
|
|
||||||
|
Widget toListTile(BuildContext context) => ListTile(
|
||||||
|
leading: CenteredLeading(Icon(icon)),
|
||||||
|
title: Text(name),
|
||||||
|
onTap: () => pushScreen(context, withNavBar: false, screen: create()),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
);
|
||||||
|
|
||||||
|
PersistentTabConfig toBottomTab(BuildContext context, {Widget Function(IconData icon)? itemBuilder}) => PersistentTabConfig(
|
||||||
|
screen: Breaker(breaker: BreakerArea.global, child: create()),
|
||||||
|
item: ItemConfig(
|
||||||
|
activeForegroundColor: Theme.of(context).primaryColor,
|
||||||
|
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
|
icon: itemBuilder == null ? Icon(icon) : itemBuilder(icon),
|
||||||
|
title: name
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Modules {
|
||||||
|
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);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'grade_averages_state.freezed.dart';
|
||||||
|
part 'grade_averages_state.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class GradeAveragesState with _$GradeAveragesState {
|
||||||
|
const factory GradeAveragesState({
|
||||||
|
required GradeAveragesGradingSystem gradingSystem,
|
||||||
|
required List<int> grades,
|
||||||
|
}) = _GradeAveragesState;
|
||||||
|
|
||||||
|
factory GradeAveragesState.fromJson(Map<String, dynamic> json) => _$GradeAveragesStateFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GradeAveragesGradingSystem {
|
||||||
|
highSchool,
|
||||||
|
middleSchool,
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
// 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 'grade_averages_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');
|
||||||
|
|
||||||
|
GradeAveragesState _$GradeAveragesStateFromJson(Map<String, dynamic> json) {
|
||||||
|
return _GradeAveragesState.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$GradeAveragesState {
|
||||||
|
GradeAveragesGradingSystem get gradingSystem =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
List<int> get grades => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$GradeAveragesStateCopyWith<GradeAveragesState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $GradeAveragesStateCopyWith<$Res> {
|
||||||
|
factory $GradeAveragesStateCopyWith(
|
||||||
|
GradeAveragesState value, $Res Function(GradeAveragesState) then) =
|
||||||
|
_$GradeAveragesStateCopyWithImpl<$Res, GradeAveragesState>;
|
||||||
|
@useResult
|
||||||
|
$Res call({GradeAveragesGradingSystem gradingSystem, List<int> grades});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$GradeAveragesStateCopyWithImpl<$Res, $Val extends GradeAveragesState>
|
||||||
|
implements $GradeAveragesStateCopyWith<$Res> {
|
||||||
|
_$GradeAveragesStateCopyWithImpl(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? gradingSystem = null,
|
||||||
|
Object? grades = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
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
|
||||||
|
as List<int>,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$GradeAveragesStateImplCopyWith<$Res>
|
||||||
|
implements $GradeAveragesStateCopyWith<$Res> {
|
||||||
|
factory _$$GradeAveragesStateImplCopyWith(_$GradeAveragesStateImpl value,
|
||||||
|
$Res Function(_$GradeAveragesStateImpl) then) =
|
||||||
|
__$$GradeAveragesStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({GradeAveragesGradingSystem gradingSystem, List<int> grades});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$GradeAveragesStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$GradeAveragesStateCopyWithImpl<$Res, _$GradeAveragesStateImpl>
|
||||||
|
implements _$$GradeAveragesStateImplCopyWith<$Res> {
|
||||||
|
__$$GradeAveragesStateImplCopyWithImpl(_$GradeAveragesStateImpl _value,
|
||||||
|
$Res Function(_$GradeAveragesStateImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? gradingSystem = null,
|
||||||
|
Object? grades = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$GradeAveragesStateImpl(
|
||||||
|
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
|
||||||
|
as List<int>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$GradeAveragesStateImpl implements _GradeAveragesState {
|
||||||
|
const _$GradeAveragesStateImpl(
|
||||||
|
{required this.gradingSystem, required final List<int> grades})
|
||||||
|
: _grades = grades;
|
||||||
|
|
||||||
|
factory _$GradeAveragesStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$GradeAveragesStateImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final GradeAveragesGradingSystem gradingSystem;
|
||||||
|
final List<int> _grades;
|
||||||
|
@override
|
||||||
|
List<int> get grades {
|
||||||
|
if (_grades is EqualUnmodifiableListView) return _grades;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_grades);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'GradeAveragesState(gradingSystem: $gradingSystem, grades: $grades)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$GradeAveragesStateImpl &&
|
||||||
|
(identical(other.gradingSystem, gradingSystem) ||
|
||||||
|
other.gradingSystem == gradingSystem) &&
|
||||||
|
const DeepCollectionEquality().equals(other._grades, _grades));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, gradingSystem, const DeepCollectionEquality().hash(_grades));
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith =>
|
||||||
|
__$$GradeAveragesStateImplCopyWithImpl<_$GradeAveragesStateImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$GradeAveragesStateImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _GradeAveragesState implements GradeAveragesState {
|
||||||
|
const factory _GradeAveragesState(
|
||||||
|
{required final GradeAveragesGradingSystem gradingSystem,
|
||||||
|
required final List<int> grades}) = _$GradeAveragesStateImpl;
|
||||||
|
|
||||||
|
factory _GradeAveragesState.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$GradeAveragesStateImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
GradeAveragesGradingSystem get gradingSystem;
|
||||||
|
@override
|
||||||
|
List<int> get grades;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$GradeAveragesStateImplCopyWith<_$GradeAveragesStateImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'grade_averages_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$GradeAveragesStateImpl _$$GradeAveragesStateImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$GradeAveragesStateImpl(
|
||||||
|
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>{
|
||||||
|
'gradingSystem':
|
||||||
|
_$GradeAveragesGradingSystemEnumMap[instance.gradingSystem]!,
|
||||||
|
'grades': instance.grades,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 GradeAveragesView extends StatelessWidget {
|
||||||
|
const GradeAveragesView({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 jeweiligen Noten aus' : 'Wähle unten die Anzahl deiner jeweiligen Punkte aus'),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Expanded(
|
||||||
|
child: GradeAveragesListView()
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart';
|
||||||
|
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
|
||||||
|
import '../repository/marianum_message_repository.dart';
|
||||||
|
import 'marianum_message_event.dart';
|
||||||
|
import 'marianum_message_state.dart';
|
||||||
|
|
||||||
|
class MarianumMessageBloc extends LoadableHydratedBloc<MarianumMessageEvent, MarianumMessageState, MarianumMessageRepository> {
|
||||||
|
@override
|
||||||
|
Future<void> gatherData() async {
|
||||||
|
var messages = await repo.getMessages();
|
||||||
|
add(Emit((state) => state.copyWith(messageList: messages)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarianumMessageRepository repository() => MarianumMessageRepository();
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarianumMessageState fromNothing() => const MarianumMessageState(messageList: MarianumMessageList(base: '', messages: []));
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarianumMessageState fromStorage(Map<String, dynamic> json) => MarianumMessageState.fromJson(json);
|
||||||
|
@override
|
||||||
|
Map<String, dynamic>? toStorage(MarianumMessageState state) => state.toJson();
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
|
||||||
|
import 'marianum_message_state.dart';
|
||||||
|
|
||||||
|
sealed class MarianumMessageEvent extends LoadableHydratedBlocEvent<MarianumMessageState> {}
|
||||||
|
class MessageEvent extends MarianumMessageEvent {}
|
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'marianum_message_state.freezed.dart';
|
||||||
|
part 'marianum_message_state.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class MarianumMessageState with _$MarianumMessageState {
|
||||||
|
const factory MarianumMessageState({
|
||||||
|
required MarianumMessageList messageList,
|
||||||
|
}) = _MarianumMessageState;
|
||||||
|
|
||||||
|
factory MarianumMessageState.fromJson(Map<String, dynamic> json) => _$MarianumMessageStateFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class MarianumMessageList with _$MarianumMessageList {
|
||||||
|
const factory MarianumMessageList({
|
||||||
|
required String base,
|
||||||
|
required List<MarianumMessage> messages,
|
||||||
|
}) = _MarianumMessageList;
|
||||||
|
|
||||||
|
factory MarianumMessageList.fromJson(Map<String, dynamic> json) => _$MarianumMessageListFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class MarianumMessage with _$MarianumMessage {
|
||||||
|
const factory MarianumMessage({
|
||||||
|
required String name,
|
||||||
|
required String date,
|
||||||
|
required String url,
|
||||||
|
}) = _MarianumMessage;
|
||||||
|
|
||||||
|
factory MarianumMessage.fromJson(Map<String, dynamic> json) => _$MarianumMessageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum GradeAveragesGradingSystem {
|
||||||
|
highSchool,
|
||||||
|
middleSchool,
|
||||||
|
}
|
@ -0,0 +1,507 @@
|
|||||||
|
// 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');
|
||||||
|
|
||||||
|
MarianumMessageState _$MarianumMessageStateFromJson(Map<String, dynamic> json) {
|
||||||
|
return _MarianumMessageState.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$MarianumMessageState {
|
||||||
|
MarianumMessageList get messageList => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => 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({MarianumMessageList messageList});
|
||||||
|
|
||||||
|
$MarianumMessageListCopyWith<$Res> get messageList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @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? messageList = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
messageList: null == messageList
|
||||||
|
? _value.messageList
|
||||||
|
: messageList // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MarianumMessageList,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$MarianumMessageListCopyWith<$Res> get messageList {
|
||||||
|
return $MarianumMessageListCopyWith<$Res>(_value.messageList, (value) {
|
||||||
|
return _then(_value.copyWith(messageList: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$MarianumMessageStateImplCopyWith<$Res>
|
||||||
|
implements $MarianumMessageStateCopyWith<$Res> {
|
||||||
|
factory _$$MarianumMessageStateImplCopyWith(_$MarianumMessageStateImpl value,
|
||||||
|
$Res Function(_$MarianumMessageStateImpl) then) =
|
||||||
|
__$$MarianumMessageStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({MarianumMessageList messageList});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$MarianumMessageListCopyWith<$Res> get messageList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @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? messageList = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$MarianumMessageStateImpl(
|
||||||
|
messageList: null == messageList
|
||||||
|
? _value.messageList
|
||||||
|
: messageList // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MarianumMessageList,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$MarianumMessageStateImpl implements _MarianumMessageState {
|
||||||
|
const _$MarianumMessageStateImpl({required this.messageList});
|
||||||
|
|
||||||
|
factory _$MarianumMessageStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$MarianumMessageStateImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final MarianumMessageList messageList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'MarianumMessageState(messageList: $messageList)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$MarianumMessageStateImpl &&
|
||||||
|
(identical(other.messageList, messageList) ||
|
||||||
|
other.messageList == messageList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, messageList);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
|
||||||
|
get copyWith =>
|
||||||
|
__$$MarianumMessageStateImplCopyWithImpl<_$MarianumMessageStateImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$MarianumMessageStateImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _MarianumMessageState implements MarianumMessageState {
|
||||||
|
const factory _MarianumMessageState(
|
||||||
|
{required final MarianumMessageList messageList}) =
|
||||||
|
_$MarianumMessageStateImpl;
|
||||||
|
|
||||||
|
factory _MarianumMessageState.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$MarianumMessageStateImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MarianumMessageList get messageList;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$MarianumMessageStateImplCopyWith<_$MarianumMessageStateImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarianumMessageList _$MarianumMessageListFromJson(Map<String, dynamic> json) {
|
||||||
|
return _MarianumMessageList.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$MarianumMessageList {
|
||||||
|
String get base => throw _privateConstructorUsedError;
|
||||||
|
List<MarianumMessage> get messages => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$MarianumMessageListCopyWith<MarianumMessageList> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $MarianumMessageListCopyWith<$Res> {
|
||||||
|
factory $MarianumMessageListCopyWith(
|
||||||
|
MarianumMessageList value, $Res Function(MarianumMessageList) then) =
|
||||||
|
_$MarianumMessageListCopyWithImpl<$Res, MarianumMessageList>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String base, List<MarianumMessage> messages});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$MarianumMessageListCopyWithImpl<$Res, $Val extends MarianumMessageList>
|
||||||
|
implements $MarianumMessageListCopyWith<$Res> {
|
||||||
|
_$MarianumMessageListCopyWithImpl(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? base = null,
|
||||||
|
Object? messages = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
base: null == base
|
||||||
|
? _value.base
|
||||||
|
: base // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
messages: null == messages
|
||||||
|
? _value.messages
|
||||||
|
: messages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<MarianumMessage>,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$MarianumMessageListImplCopyWith<$Res>
|
||||||
|
implements $MarianumMessageListCopyWith<$Res> {
|
||||||
|
factory _$$MarianumMessageListImplCopyWith(_$MarianumMessageListImpl value,
|
||||||
|
$Res Function(_$MarianumMessageListImpl) then) =
|
||||||
|
__$$MarianumMessageListImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String base, List<MarianumMessage> messages});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$MarianumMessageListImplCopyWithImpl<$Res>
|
||||||
|
extends _$MarianumMessageListCopyWithImpl<$Res, _$MarianumMessageListImpl>
|
||||||
|
implements _$$MarianumMessageListImplCopyWith<$Res> {
|
||||||
|
__$$MarianumMessageListImplCopyWithImpl(_$MarianumMessageListImpl _value,
|
||||||
|
$Res Function(_$MarianumMessageListImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? base = null,
|
||||||
|
Object? messages = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$MarianumMessageListImpl(
|
||||||
|
base: null == base
|
||||||
|
? _value.base
|
||||||
|
: base // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
messages: null == messages
|
||||||
|
? _value._messages
|
||||||
|
: messages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<MarianumMessage>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$MarianumMessageListImpl implements _MarianumMessageList {
|
||||||
|
const _$MarianumMessageListImpl(
|
||||||
|
{required this.base, required final List<MarianumMessage> messages})
|
||||||
|
: _messages = messages;
|
||||||
|
|
||||||
|
factory _$MarianumMessageListImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$MarianumMessageListImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String base;
|
||||||
|
final List<MarianumMessage> _messages;
|
||||||
|
@override
|
||||||
|
List<MarianumMessage> get messages {
|
||||||
|
if (_messages is EqualUnmodifiableListView) return _messages;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'MarianumMessageList(base: $base, messages: $messages)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$MarianumMessageListImpl &&
|
||||||
|
(identical(other.base, base) || other.base == base) &&
|
||||||
|
const DeepCollectionEquality().equals(other._messages, _messages));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, base, const DeepCollectionEquality().hash(_messages));
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith =>
|
||||||
|
__$$MarianumMessageListImplCopyWithImpl<_$MarianumMessageListImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$MarianumMessageListImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _MarianumMessageList implements MarianumMessageList {
|
||||||
|
const factory _MarianumMessageList(
|
||||||
|
{required final String base,
|
||||||
|
required final List<MarianumMessage> messages}) =
|
||||||
|
_$MarianumMessageListImpl;
|
||||||
|
|
||||||
|
factory _MarianumMessageList.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$MarianumMessageListImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get base;
|
||||||
|
@override
|
||||||
|
List<MarianumMessage> get messages;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$MarianumMessageListImplCopyWith<_$MarianumMessageListImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarianumMessage _$MarianumMessageFromJson(Map<String, dynamic> json) {
|
||||||
|
return _MarianumMessage.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$MarianumMessage {
|
||||||
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
String get date => throw _privateConstructorUsedError;
|
||||||
|
String get url => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$MarianumMessageCopyWith<MarianumMessage> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $MarianumMessageCopyWith<$Res> {
|
||||||
|
factory $MarianumMessageCopyWith(
|
||||||
|
MarianumMessage value, $Res Function(MarianumMessage) then) =
|
||||||
|
_$MarianumMessageCopyWithImpl<$Res, MarianumMessage>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String name, String date, String url});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$MarianumMessageCopyWithImpl<$Res, $Val extends MarianumMessage>
|
||||||
|
implements $MarianumMessageCopyWith<$Res> {
|
||||||
|
_$MarianumMessageCopyWithImpl(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? name = null,
|
||||||
|
Object? date = null,
|
||||||
|
Object? url = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
date: null == date
|
||||||
|
? _value.date
|
||||||
|
: date // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$MarianumMessageImplCopyWith<$Res>
|
||||||
|
implements $MarianumMessageCopyWith<$Res> {
|
||||||
|
factory _$$MarianumMessageImplCopyWith(_$MarianumMessageImpl value,
|
||||||
|
$Res Function(_$MarianumMessageImpl) then) =
|
||||||
|
__$$MarianumMessageImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String name, String date, String url});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$MarianumMessageImplCopyWithImpl<$Res>
|
||||||
|
extends _$MarianumMessageCopyWithImpl<$Res, _$MarianumMessageImpl>
|
||||||
|
implements _$$MarianumMessageImplCopyWith<$Res> {
|
||||||
|
__$$MarianumMessageImplCopyWithImpl(
|
||||||
|
_$MarianumMessageImpl _value, $Res Function(_$MarianumMessageImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? name = null,
|
||||||
|
Object? date = null,
|
||||||
|
Object? url = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$MarianumMessageImpl(
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
date: null == date
|
||||||
|
? _value.date
|
||||||
|
: date // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$MarianumMessageImpl implements _MarianumMessage {
|
||||||
|
const _$MarianumMessageImpl(
|
||||||
|
{required this.name, required this.date, required this.url});
|
||||||
|
|
||||||
|
factory _$MarianumMessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$MarianumMessageImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String date;
|
||||||
|
@override
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'MarianumMessage(name: $name, date: $date, url: $url)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$MarianumMessageImpl &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.date, date) || other.date == date) &&
|
||||||
|
(identical(other.url, url) || other.url == url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, name, date, url);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith =>
|
||||||
|
__$$MarianumMessageImplCopyWithImpl<_$MarianumMessageImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$MarianumMessageImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _MarianumMessage implements MarianumMessage {
|
||||||
|
const factory _MarianumMessage(
|
||||||
|
{required final String name,
|
||||||
|
required final String date,
|
||||||
|
required final String url}) = _$MarianumMessageImpl;
|
||||||
|
|
||||||
|
factory _MarianumMessage.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$MarianumMessageImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name;
|
||||||
|
@override
|
||||||
|
String get date;
|
||||||
|
@override
|
||||||
|
String get url;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$MarianumMessageImplCopyWith<_$MarianumMessageImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'marianum_message_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$MarianumMessageStateImpl _$$MarianumMessageStateImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$MarianumMessageStateImpl(
|
||||||
|
messageList: MarianumMessageList.fromJson(
|
||||||
|
json['messageList'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$MarianumMessageStateImplToJson(
|
||||||
|
_$MarianumMessageStateImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'messageList': instance.messageList,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$MarianumMessageListImpl _$$MarianumMessageListImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$MarianumMessageListImpl(
|
||||||
|
base: json['base'] as String,
|
||||||
|
messages: (json['messages'] as List<dynamic>)
|
||||||
|
.map((e) => MarianumMessage.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$MarianumMessageListImplToJson(
|
||||||
|
_$MarianumMessageListImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'base': instance.base,
|
||||||
|
'messages': instance.messages,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$MarianumMessageImpl _$$MarianumMessageImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$MarianumMessageImpl(
|
||||||
|
name: json['name'] as String,
|
||||||
|
date: json['date'] as String,
|
||||||
|
url: json['url'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$MarianumMessageImplToJson(
|
||||||
|
_$MarianumMessageImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': instance.name,
|
||||||
|
'date': instance.date,
|
||||||
|
'url': instance.url,
|
||||||
|
};
|
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import '../../../infrastructure/dataLoader/data_loader.dart';
|
||||||
|
import '../../../infrastructure/dataLoader/mhsl_data_loader.dart';
|
||||||
|
import '../bloc/marianum_message_state.dart';
|
||||||
|
|
||||||
|
class MarianumMessageGetMessages extends MhslDataLoader<MarianumMessageList> {
|
||||||
|
@override
|
||||||
|
Future<Response<String>> fetch() async => dio.get('/message/messages.json');
|
||||||
|
@override
|
||||||
|
MarianumMessageList assemble(DataLoaderResult data) => MarianumMessageList.fromJson(data.json);
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import '../../../infrastructure/repository/repository.dart';
|
||||||
|
import '../bloc/marianum_message_state.dart';
|
||||||
|
import '../dataProvider/marianum_message_get_messages.dart';
|
||||||
|
|
||||||
|
class MarianumMessageRepository extends Repository<MarianumMessageState> {
|
||||||
|
Future<MarianumMessageList> getMessages() => MarianumMessageGetMessages().run();
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'marianum_message_view.dart';
|
||||||
|
import '../../../infrastructure/loadableState/loadable_state.dart';
|
||||||
|
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
|
||||||
|
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
|
||||||
|
import '../bloc/marianum_message_bloc.dart';
|
||||||
|
import '../bloc/marianum_message_state.dart';
|
||||||
|
|
||||||
|
class MarianumMessageListView extends StatelessWidget {
|
||||||
|
const MarianumMessageListView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => BlocModule<MarianumMessageBloc, LoadableState<MarianumMessageState>>(
|
||||||
|
create: (context) => MarianumMessageBloc(),
|
||||||
|
child: (context, bloc, state) => Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Marianum Message'),
|
||||||
|
),
|
||||||
|
body: LoadableStateConsumer<MarianumMessageBloc, MarianumMessageState>(
|
||||||
|
child: (state, loading) => ListView.builder(
|
||||||
|
itemCount: state.messageList.messages.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
var message = state.messageList.messages.toList()[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: const Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [Icon(Icons.newspaper)],
|
||||||
|
),
|
||||||
|
title: Text(message.name, overflow: TextOverflow.ellipsis),
|
||||||
|
subtitle: Text('vom ${message.date}'),
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: state.messageList.base, message: message)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../../../api/mhsl/message/getMessages/getMessagesResponse.dart';
|
import '../bloc/marianum_message_state.dart';
|
||||||
import '../../../../widget/confirmDialog.dart';
|
import '../../../../../widget/confirmDialog.dart';
|
||||||
|
|
||||||
class MessageView extends StatefulWidget {
|
class MessageView extends StatefulWidget {
|
||||||
final String basePath;
|
final String basePath;
|
||||||
final GetMessagesResponseObject message;
|
final MarianumMessage message;
|
||||||
const MessageView({super.key, required this.basePath, required this.message});
|
const MessageView({super.key, required this.basePath, required this.message});
|
||||||
|
|
||||||
@override
|
@override
|
@ -99,7 +99,7 @@ class _FileElementState extends State<FileElement> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if(widget.file.isDirectory) {
|
if(widget.file.isDirectory) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
builder: (context) => Files(widget.path.toList()..add(widget.file.name)),
|
builder: (context) => Files(path: widget.path.toList()..add(widget.file.name)),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
||||||
|
@ -21,7 +21,7 @@ import 'filesUploadDialog.dart';
|
|||||||
|
|
||||||
class Files extends StatefulWidget {
|
class Files extends StatefulWidget {
|
||||||
final List<String> path;
|
final List<String> path;
|
||||||
const Files(this.path, {super.key});
|
Files({List<String>? path, super.key}) : path = path ?? [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Files> createState() => _FilesState();
|
State<Files> createState() => _FilesState();
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../../../model/message/messageProps.dart';
|
|
||||||
import '../../../../widget/loadingSpinner.dart';
|
|
||||||
import 'messageView.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class Message extends StatefulWidget {
|
|
||||||
const Message({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<Message> createState() => _MessageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MessageState extends State<Message> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
||||||
Provider.of<MessageProps>(context, listen: false).run();
|
|
||||||
});
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Marianum Message'),
|
|
||||||
),
|
|
||||||
body: Consumer<MessageProps>(builder: (context, value, child) {
|
|
||||||
if(value.primaryLoading()) return const LoadingSpinner();
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: value.getMessagesResponse.messages.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
var message = value.getMessagesResponse.messages.toList()[index];
|
|
||||||
return ListTile(
|
|
||||||
leading: const Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [Icon(Icons.newspaper)],
|
|
||||||
),
|
|
||||||
title: Text(message.name, overflow: TextOverflow.ellipsis),
|
|
||||||
subtitle: Text('vom ${message.date}'),
|
|
||||||
trailing: const Icon(Icons.arrow_right),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: value.getMessagesResponse.base, message: message)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onRefresh: () {
|
|
||||||
Provider.of<MessageProps>(context, listen: false).run(renew: true);
|
|
||||||
return Future.delayed(const Duration(seconds: 3));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
@ -6,15 +6,11 @@ import 'package:in_app_review/in_app_review.dart';
|
|||||||
import '../../extensions/renderNotNull.dart';
|
import '../../extensions/renderNotNull.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
|
|
||||||
import '../../widget/ListItem.dart';
|
import '../../state/app/modules/app_modules.dart';
|
||||||
import '../../widget/centeredLeading.dart';
|
import '../../widget/centeredLeading.dart';
|
||||||
import '../../widget/infoDialog.dart';
|
import '../../widget/infoDialog.dart';
|
||||||
import '../settings/settings.dart';
|
import '../settings/settings.dart';
|
||||||
import 'more/feedback/feedbackDialog.dart';
|
import 'more/feedback/feedbackDialog.dart';
|
||||||
import 'more/gradeAverages/gradeAverage.dart';
|
|
||||||
import 'more/holidays/holidays.dart';
|
|
||||||
import 'more/message/message.dart';
|
|
||||||
import 'more/roomplan/roomplan.dart';
|
|
||||||
import 'more/share/selectShareTypeDialog.dart';
|
import 'more/share/selectShareTypeDialog.dart';
|
||||||
|
|
||||||
class Overhang extends StatelessWidget {
|
class Overhang extends StatelessWidget {
|
||||||
@ -30,11 +26,13 @@ class Overhang extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
const ListItemNavigator(icon: Icons.newspaper, text: 'Marianum Message', target: Message()),
|
AppModule.getModule(Modules.marianumMessage).toListTile(context),
|
||||||
const ListItemNavigator(icon: Icons.room, text: 'Raumplan', target: Roomplan()),
|
AppModule.getModule(Modules.roomPlan).toListTile(context),
|
||||||
const ListItemNavigator(icon: Icons.calculate, text: 'Notendurschnittsrechner', target: GradeAverage()),
|
AppModule.getModule(Modules.gradeAveragesCalculator).toListTile(context),
|
||||||
const ListItemNavigator(icon: Icons.calendar_month, text: 'Schulferien', target: Holidays()),
|
AppModule.getModule(Modules.holidays).toListTile(context),
|
||||||
|
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.share_outlined),
|
leading: const Icon(Icons.share_outlined),
|
||||||
title: const Text('Teile die App'),
|
title: const Text('Teile die App'),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import 'package:filesize/filesize.dart';
|
import 'package:filesize/filesize.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../storage/base/settingsProvider.dart';
|
import '../../storage/base/settingsProvider.dart';
|
||||||
@ -109,6 +110,23 @@ class _DevToolsSettingsDialogState extends State<DevToolsSettingsDialog> {
|
|||||||
},
|
},
|
||||||
trailing: const Icon(Icons.arrow_right),
|
trailing: const Icon(Icons.arrow_right),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const CenteredLeading(Icon(Icons.data_object)),
|
||||||
|
title: const Text('BLOC State cache'),
|
||||||
|
subtitle: const Text('Lange tippen um zu löschen'),
|
||||||
|
onTap: () {
|
||||||
|
// Navigator.push(context, MaterialPageRoute(builder: (context) => const CacheView()));
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
ConfirmDialog(
|
||||||
|
title: 'BLOC-Cache löschen',
|
||||||
|
content: 'Alle cache Einträge werden gelöscht. Der Cache wird bei Nutzung der App automatisch erneut aufgebaut',
|
||||||
|
confirmButton: 'Unwiederruflich löschen',
|
||||||
|
onConfirm: () => HydratedBloc.storage.clear(),
|
||||||
|
).asDialog(context);
|
||||||
|
},
|
||||||
|
trailing: const Icon(Icons.arrow_right),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
|
||||||
|
|
||||||
class ListItemNavigator extends StatelessWidget {
|
|
||||||
const ListItemNavigator({super.key, required this.icon, required this.text, required this.target, this.onLongPress, this.arrow = true});
|
|
||||||
|
|
||||||
final IconData icon;
|
|
||||||
final String text;
|
|
||||||
final bool arrow;
|
|
||||||
|
|
||||||
final Widget target;
|
|
||||||
|
|
||||||
final GestureLongPressCallback? onLongPress;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
onTabAction() => pushScreen(context, withNavBar: false, screen: target); //Navigator.push(context, MaterialPageRoute(builder: (context) => target));
|
|
||||||
onLongPressAction() => onLongPress;
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
leading: Icon(icon),
|
|
||||||
title: Text(text),
|
|
||||||
trailing: arrow ? const Icon(Icons.arrow_right) : null,
|
|
||||||
onTap: onTabAction,
|
|
||||||
onLongPress: onLongPressAction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
17
lib/widget/conditional_wrapper.dart
Normal file
17
lib/widget/conditional_wrapper.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ConditionalWrapper extends StatelessWidget {
|
||||||
|
final bool condition;
|
||||||
|
final Widget child;
|
||||||
|
final Widget Function(Widget child) wrapper;
|
||||||
|
|
||||||
|
const ConditionalWrapper({
|
||||||
|
required this.condition,
|
||||||
|
required this.child,
|
||||||
|
required this.wrapper,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => condition ? wrapper(child) : child;
|
||||||
|
}
|
13
pubspec.yaml
13
pubspec.yaml
@ -49,7 +49,7 @@ dependencies:
|
|||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
provider: ^6.0.4
|
provider: ^6.0.4
|
||||||
jiffy: ^6.1.0
|
jiffy: ^6.1.0
|
||||||
json_annotation: ^4.8.0
|
json_annotation: ^4.8.1
|
||||||
localstore: ^1.2.3
|
localstore: ^1.2.3
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
nextcloud:
|
nextcloud:
|
||||||
@ -98,12 +98,18 @@ dependencies:
|
|||||||
time_range_picker: ^2.2.0
|
time_range_picker: ^2.2.0
|
||||||
in_app_review: ^2.0.8
|
in_app_review: ^2.0.8
|
||||||
emoji_picker_flutter: ^2.1.1
|
emoji_picker_flutter: ^2.1.1
|
||||||
|
bloc: ^8.1.4
|
||||||
|
flutter_bloc: ^8.1.5
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
|
connectivity_plus: ^6.0.3
|
||||||
|
hydrated_bloc: ^9.1.5
|
||||||
|
dio: ^4.0.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
json_serializable: ^6.6.1
|
json_serializable: ^6.7.1
|
||||||
build_runner: ^2.3.3
|
build_runner: ^2.4.9
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
@ -111,6 +117,7 @@ dev_dependencies:
|
|||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^3.0.1
|
||||||
|
freezed: ^2.5.2
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
Loading…
x
Reference in New Issue
Block a user