wip: bloc for files

This commit is contained in:
2024-08-17 11:41:38 +02:00
parent ddeeaeaeac
commit 54b777237f
42 changed files with 880 additions and 148 deletions

View File

@ -8,6 +8,7 @@ import '../../../view/pages/more/roomplan/roomplan.dart';
import '../../../view/pages/talk/chatList.dart';
import '../../../view/pages/timetable/timetable.dart';
import '../../../widget/centeredLeading.dart';
import 'files/view/files_view.dart';
import 'gradeAverages/view/grade_averages_view.dart';
import 'holidays/view/holidays_view.dart';
import 'marianumMessage/view/marianum_message_list_view.dart';
@ -23,6 +24,7 @@ class AppModule {
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.blocFiles: AppModule('BlocFiles', Icons.folder, FilesView.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),
@ -53,6 +55,7 @@ enum Modules {
timetable,
talk,
files,
blocFiles,
marianumMessage,
roomPlan,
gradeAveragesCalculator,

View File

@ -0,0 +1,65 @@
import 'dart:developer';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import '../repository/files_repository.dart';
import 'files_event.dart';
import 'files_state.dart';
class FilesBloc extends LoadableHydratedBloc<FilesEvent, FilesState, FilesRepository> {
static const String basePath = '/';
FilesBloc() {
add(Emit((state) => state.copyWith(currentFolder: basePath)));
on<EnterFolder>((event, emit) {
add(Emit((state) {
log('setFolder');
return state.copyWith(currentFolder: event.absolutePath);
}, fetch: true));
});
}
List<File>? getVisibleFiles() => innerState?.files[innerState?.currentFolder];
String getCurrentFolder() => innerState?.currentFolder ?? basePath;
String getCurrentFolderName() {
var folder = innerState?.currentFolder.split('/').reversed.elementAt(1);
return folder!.isEmpty ? 'Dateien' : folder;
}
bool canGoBack() => innerState?.currentFolder != basePath;
String goBackLocation() {
var pathSegments = innerState?.currentFolder.split(basePath) ?? [];
if (pathSegments.isNotEmpty) {
pathSegments.removeLast();
pathSegments.removeLast();
}
return pathSegments.join(basePath) + basePath;
}
@override
FilesState fromNothing() => const FilesState(currentFolder: basePath, files: {});
@override
FilesState fromStorage(Map<String, dynamic> json) => FilesState.fromJson(json);
@override
Future<void> gatherData() async {
var fetchFolder = getCurrentFolder();
log(fetchFolder);
var files = await repo.getFileList(fetchFolder);
var newFileMap = Map.of(innerState?.files ?? <String, List<File>>{});
newFileMap[fetchFolder] = files;
if(fetchFolder != getCurrentFolder()) {
log('Fetch aborted due to folder change (expected "$fetchFolder" got "${getCurrentFolder()}")');
return;
}
add(DataGathered((state) => state.copyWith(files: newFileMap)));
}
@override
FilesRepository repository() => FilesRepository();
@override
Map<String, dynamic>? toStorage(FilesState state) => state.toJson();
}

View File

@ -0,0 +1,10 @@
import '../../../infrastructure/utilityWidgets/loadableHydratedBloc/loadable_hydrated_bloc_event.dart';
import 'files_state.dart';
sealed class FilesEvent extends LoadableHydratedBlocEvent<FilesState> {}
class EnterFolder extends FilesEvent {
String absolutePath;
EnterFolder(this.absolutePath);
}

View File

@ -0,0 +1,29 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'files_state.freezed.dart';
part 'files_state.g.dart';
@freezed
class FilesState with _$FilesState {
const factory FilesState({
required String currentFolder,
required Map<String, List<File>> files,
}) = _FilesState;
factory FilesState.fromJson(Map<String, Object?> json) => _$FilesStateFromJson(json);
}
@freezed
class File with _$File {
const factory File({
required String path,
required bool isFolder,
required String name,
required DateTime? createdAt,
required DateTime? updatedAt,
required int? size,
required String? mimeType,
}) = _File;
factory File.fromJson(Map<String, Object?> json) => _$FileFromJson(json);
}

View File

@ -0,0 +1,439 @@
// 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 'files_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');
FilesState _$FilesStateFromJson(Map<String, dynamic> json) {
return _FilesState.fromJson(json);
}
/// @nodoc
mixin _$FilesState {
String get currentFolder => throw _privateConstructorUsedError;
Map<String, List<File>> get files => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FilesStateCopyWith<FilesState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FilesStateCopyWith<$Res> {
factory $FilesStateCopyWith(
FilesState value, $Res Function(FilesState) then) =
_$FilesStateCopyWithImpl<$Res, FilesState>;
@useResult
$Res call({String currentFolder, Map<String, List<File>> files});
}
/// @nodoc
class _$FilesStateCopyWithImpl<$Res, $Val extends FilesState>
implements $FilesStateCopyWith<$Res> {
_$FilesStateCopyWithImpl(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? currentFolder = null,
Object? files = null,
}) {
return _then(_value.copyWith(
currentFolder: null == currentFolder
? _value.currentFolder
: currentFolder // ignore: cast_nullable_to_non_nullable
as String,
files: null == files
? _value.files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, List<File>>,
) as $Val);
}
}
/// @nodoc
abstract class _$$FilesStateImplCopyWith<$Res>
implements $FilesStateCopyWith<$Res> {
factory _$$FilesStateImplCopyWith(
_$FilesStateImpl value, $Res Function(_$FilesStateImpl) then) =
__$$FilesStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String currentFolder, Map<String, List<File>> files});
}
/// @nodoc
class __$$FilesStateImplCopyWithImpl<$Res>
extends _$FilesStateCopyWithImpl<$Res, _$FilesStateImpl>
implements _$$FilesStateImplCopyWith<$Res> {
__$$FilesStateImplCopyWithImpl(
_$FilesStateImpl _value, $Res Function(_$FilesStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? currentFolder = null,
Object? files = null,
}) {
return _then(_$FilesStateImpl(
currentFolder: null == currentFolder
? _value.currentFolder
: currentFolder // ignore: cast_nullable_to_non_nullable
as String,
files: null == files
? _value._files
: files // ignore: cast_nullable_to_non_nullable
as Map<String, List<File>>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$FilesStateImpl implements _FilesState {
const _$FilesStateImpl(
{required this.currentFolder,
required final Map<String, List<File>> files})
: _files = files;
factory _$FilesStateImpl.fromJson(Map<String, dynamic> json) =>
_$$FilesStateImplFromJson(json);
@override
final String currentFolder;
final Map<String, List<File>> _files;
@override
Map<String, List<File>> get files {
if (_files is EqualUnmodifiableMapView) return _files;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_files);
}
@override
String toString() {
return 'FilesState(currentFolder: $currentFolder, files: $files)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FilesStateImpl &&
(identical(other.currentFolder, currentFolder) ||
other.currentFolder == currentFolder) &&
const DeepCollectionEquality().equals(other._files, _files));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, currentFolder, const DeepCollectionEquality().hash(_files));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FilesStateImplCopyWith<_$FilesStateImpl> get copyWith =>
__$$FilesStateImplCopyWithImpl<_$FilesStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$FilesStateImplToJson(
this,
);
}
}
abstract class _FilesState implements FilesState {
const factory _FilesState(
{required final String currentFolder,
required final Map<String, List<File>> files}) = _$FilesStateImpl;
factory _FilesState.fromJson(Map<String, dynamic> json) =
_$FilesStateImpl.fromJson;
@override
String get currentFolder;
@override
Map<String, List<File>> get files;
@override
@JsonKey(ignore: true)
_$$FilesStateImplCopyWith<_$FilesStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
File _$FileFromJson(Map<String, dynamic> json) {
return _File.fromJson(json);
}
/// @nodoc
mixin _$File {
String get path => throw _privateConstructorUsedError;
bool get isFolder => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
DateTime? get createdAt => throw _privateConstructorUsedError;
DateTime? get updatedAt => throw _privateConstructorUsedError;
int? get size => throw _privateConstructorUsedError;
String? get mimeType => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$FileCopyWith<File> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $FileCopyWith<$Res> {
factory $FileCopyWith(File value, $Res Function(File) then) =
_$FileCopyWithImpl<$Res, File>;
@useResult
$Res call(
{String path,
bool isFolder,
String name,
DateTime? createdAt,
DateTime? updatedAt,
int? size,
String? mimeType});
}
/// @nodoc
class _$FileCopyWithImpl<$Res, $Val extends File>
implements $FileCopyWith<$Res> {
_$FileCopyWithImpl(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? path = null,
Object? isFolder = null,
Object? name = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? size = freezed,
Object? mimeType = freezed,
}) {
return _then(_value.copyWith(
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
isFolder: null == isFolder
? _value.isFolder
: isFolder // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
size: freezed == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int?,
mimeType: freezed == mimeType
? _value.mimeType
: mimeType // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$FileImplCopyWith<$Res> implements $FileCopyWith<$Res> {
factory _$$FileImplCopyWith(
_$FileImpl value, $Res Function(_$FileImpl) then) =
__$$FileImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String path,
bool isFolder,
String name,
DateTime? createdAt,
DateTime? updatedAt,
int? size,
String? mimeType});
}
/// @nodoc
class __$$FileImplCopyWithImpl<$Res>
extends _$FileCopyWithImpl<$Res, _$FileImpl>
implements _$$FileImplCopyWith<$Res> {
__$$FileImplCopyWithImpl(_$FileImpl _value, $Res Function(_$FileImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? path = null,
Object? isFolder = null,
Object? name = null,
Object? createdAt = freezed,
Object? updatedAt = freezed,
Object? size = freezed,
Object? mimeType = freezed,
}) {
return _then(_$FileImpl(
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
as String,
isFolder: null == isFolder
? _value.isFolder
: isFolder // ignore: cast_nullable_to_non_nullable
as bool,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
createdAt: freezed == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
updatedAt: freezed == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
as DateTime?,
size: freezed == size
? _value.size
: size // ignore: cast_nullable_to_non_nullable
as int?,
mimeType: freezed == mimeType
? _value.mimeType
: mimeType // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$FileImpl implements _File {
const _$FileImpl(
{required this.path,
required this.isFolder,
required this.name,
required this.createdAt,
required this.updatedAt,
required this.size,
required this.mimeType});
factory _$FileImpl.fromJson(Map<String, dynamic> json) =>
_$$FileImplFromJson(json);
@override
final String path;
@override
final bool isFolder;
@override
final String name;
@override
final DateTime? createdAt;
@override
final DateTime? updatedAt;
@override
final int? size;
@override
final String? mimeType;
@override
String toString() {
return 'File(path: $path, isFolder: $isFolder, name: $name, createdAt: $createdAt, updatedAt: $updatedAt, size: $size, mimeType: $mimeType)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$FileImpl &&
(identical(other.path, path) || other.path == path) &&
(identical(other.isFolder, isFolder) ||
other.isFolder == isFolder) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt) &&
(identical(other.updatedAt, updatedAt) ||
other.updatedAt == updatedAt) &&
(identical(other.size, size) || other.size == size) &&
(identical(other.mimeType, mimeType) ||
other.mimeType == mimeType));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, path, isFolder, name, createdAt, updatedAt, size, mimeType);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FileImplCopyWith<_$FileImpl> get copyWith =>
__$$FileImplCopyWithImpl<_$FileImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$FileImplToJson(
this,
);
}
}
abstract class _File implements File {
const factory _File(
{required final String path,
required final bool isFolder,
required final String name,
required final DateTime? createdAt,
required final DateTime? updatedAt,
required final int? size,
required final String? mimeType}) = _$FileImpl;
factory _File.fromJson(Map<String, dynamic> json) = _$FileImpl.fromJson;
@override
String get path;
@override
bool get isFolder;
@override
String get name;
@override
DateTime? get createdAt;
@override
DateTime? get updatedAt;
@override
int? get size;
@override
String? get mimeType;
@override
@JsonKey(ignore: true)
_$$FileImplCopyWith<_$FileImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'files_state.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$FilesStateImpl _$$FilesStateImplFromJson(Map<String, dynamic> json) =>
_$FilesStateImpl(
currentFolder: json['currentFolder'] as String,
files: (json['files'] as Map<String, dynamic>).map(
(k, e) => MapEntry(
k,
(e as List<dynamic>)
.map((e) => File.fromJson(e as Map<String, dynamic>))
.toList()),
),
);
Map<String, dynamic> _$$FilesStateImplToJson(_$FilesStateImpl instance) =>
<String, dynamic>{
'currentFolder': instance.currentFolder,
'files': instance.files,
};
_$FileImpl _$$FileImplFromJson(Map<String, dynamic> json) => _$FileImpl(
path: json['path'] as String,
isFolder: json['isFolder'] as bool,
name: json['name'] as String,
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
updatedAt: json['updatedAt'] == null
? null
: DateTime.parse(json['updatedAt'] as String),
size: (json['size'] as num?)?.toInt(),
mimeType: json['mimeType'] as String?,
);
Map<String, dynamic> _$$FileImplToJson(_$FileImpl instance) =>
<String, dynamic>{
'path': instance.path,
'isFolder': instance.isFolder,
'name': instance.name,
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
'size': instance.size,
'mimeType': instance.mimeType,
};

View File

@ -0,0 +1,31 @@
import 'package:nextcloud/nextcloud.dart';
import '../../../../../api/marianumcloud/webdav/webdavApi.dart';
import '../../../infrastructure/repository/repository.dart';
import '../bloc/files_state.dart';
class FilesRepository extends Repository<FilesState> {
Future<List<File>> getFileList(String path) async {
var webdav = await WebdavApi.webdav;
var response = await webdav.propfind(PathUri.parse(path)); // TODO move to custom data loader
var davFiles = response.toWebDavFiles();
davFiles.removeWhere((file) {
var filePath = file.path.path;
return filePath.isEmpty || filePath == path;
});
var files = davFiles.map((davFile) => File(
path: davFile.path.path,
isFolder: davFile.isDirectory,
name: davFile.name,
createdAt: davFile.createdDate,
updatedAt: davFile.lastModified,
size: davFile.size,
mimeType: davFile.mimeType
));
return files.toList();
}
}

View File

@ -0,0 +1,67 @@
import 'dart:developer';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import '../../../../../widget/centeredLeading.dart';
import '../../../../../widget/list_view_util.dart';
import '../../../infrastructure/loadableState/loadable_state.dart';
import '../../../infrastructure/loadableState/view/loadable_state_consumer.dart';
import '../../../infrastructure/utilityWidgets/bloc_module.dart';
import '../bloc/files_bloc.dart';
import '../bloc/files_event.dart';
import '../bloc/files_state.dart';
class FilesView extends StatelessWidget {
const FilesView({super.key});
@override
Widget build(BuildContext context) => BlocModule<FilesBloc, LoadableState<FilesState>>(
create: (context) => FilesBloc(),
autoRebuild: true,
child: (context, bloc, state) {
var goBackButton = !bloc.canGoBack() ? null : IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
bloc.add(EnterFolder(bloc.goBackLocation()));
},
);
return PopScope(
canPop: false,
onPopInvoked: (didPop) => bloc.add(EnterFolder(bloc.goBackLocation())),
child: Scaffold(
appBar: AppBar(
leading: goBackButton,
title: Text(bloc.getCurrentFolderName()),
actions: [
IconButton(onPressed: () {
log(bloc.innerState?.toJson().toString() ?? 'leer');
}, icon: const Icon(Icons.bug_report)),
IconButton(onPressed: () {
bloc.add(EnterFolder('/'));
}, icon: const Icon(Icons.home)),
],
),
body: LoadableStateConsumer<FilesBloc, FilesState>(
child: (state, loading) => ListViewUtil.fromList<File>(bloc.getVisibleFiles(), (file) => ListTile(
leading: CenteredLeading(Icon(file.isFolder ? Icons.folder : Icons.description_outlined)),
title: Text(file.name),
subtitle: file.isFolder
? Text('geändert ${Jiffy.parseFromDateTime(file.updatedAt ?? DateTime.now()).fromNow()}')
: Text('${filesize(file.size)}, ${Jiffy.parseFromDateTime(file.updatedAt ?? DateTime.now()).fromNow()}'),
trailing: Icon(file.isFolder ? Icons.arrow_right : null),
onTap: () {
log(file.path);
if(!file.isFolder) return;
bloc.add(EnterFolder(file.path));
},
))
)
),
);
},
);
}

View File

@ -11,7 +11,9 @@ _$GradeAveragesStateImpl _$$GradeAveragesStateImplFromJson(
_$GradeAveragesStateImpl(
gradingSystem: $enumDecode(
_$GradeAveragesGradingSystemEnumMap, json['gradingSystem']),
grades: (json['grades'] as List<dynamic>).map((e) => e as int).toList(),
grades: (json['grades'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
);
Map<String, dynamic> _$$GradeAveragesStateImplToJson(

View File

@ -26,7 +26,7 @@ _$HolidayImpl _$$HolidayImplFromJson(Map<String, dynamic> json) =>
_$HolidayImpl(
start: json['start'] as String,
end: json['end'] as String,
year: json['year'] as int,
year: (json['year'] as num).toInt(),
stateCode: json['stateCode'] as String,
name: json['name'] as String,
slug: json['slug'] as String,

View File

@ -1,7 +1,7 @@
import 'package:dio/dio.dart';
import '../../../basis/dataloader/holiday_data_loader.dart';
import '../../../infrastructure/dataLoader/data_loader.dart';
import '../../../infrastructure/dataLoader/http_data_loader.dart';
import '../bloc/holidays_state.dart';
class HolidaysGetHolidays extends HolidayDataLoader<List<Holiday>> {

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import '../../../infrastructure/dataLoader/data_loader.dart';
import '../../../infrastructure/dataLoader/http_data_loader.dart';
import '../../../basis/dataloader/mhsl_data_loader.dart';
import '../bloc/marianum_message_state.dart';