claude refactorings, flutter best practices, platform dependent changes, general cleanup
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
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;
|
||||
final void Function(BuildContext context, TBloc bloc)? onInitialisation;
|
||||
const BlocModule({required this.create, required this.child, this.autoRebuild = false, this.onInitialisation, 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: (context) {
|
||||
final bloc = create(context);
|
||||
onInitialisation?.call(context, bloc);
|
||||
return bloc;
|
||||
},
|
||||
child: Builder(
|
||||
builder: (context) => autoRebuild
|
||||
? rebuildChild(context)
|
||||
: staticChild(context)
|
||||
)
|
||||
);
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
|
||||
import '../../../../../api/errors/error_mapper.dart';
|
||||
import '../../loadable_state/loadable_state.dart';
|
||||
import '../../loadable_state/loading_error.dart';
|
||||
import '../../repository/repository.dart';
|
||||
import 'loadable_hydrated_bloc_event.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(
|
||||
error: null,
|
||||
data: null,
|
||||
isLoading: true,
|
||||
lastFetch: null,
|
||||
reFetch: null,
|
||||
)) {
|
||||
|
||||
on<Emit<TState>>((event, emit) {
|
||||
emit(LoadableState(
|
||||
isLoading: state.isLoading,
|
||||
data: event.state(innerState ?? fromNothing()),
|
||||
lastFetch: state.lastFetch,
|
||||
reFetch: retry,
|
||||
error: state.error,
|
||||
));
|
||||
});
|
||||
|
||||
on<DataGathered<TState>>((event, emit) => emit(LoadableState(
|
||||
isLoading: false,
|
||||
data: event.state(innerState ?? fromNothing()),
|
||||
lastFetch: DateTime.now().millisecondsSinceEpoch,
|
||||
reFetch: retry,
|
||||
error: null,
|
||||
)));
|
||||
|
||||
on<RefetchStarted<TState>>((event, emit) => emit(LoadableState(
|
||||
isLoading: true,
|
||||
data: innerState,
|
||||
lastFetch: state.lastFetch,
|
||||
reFetch: null,
|
||||
error: null,
|
||||
)));
|
||||
|
||||
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: errorToUserMessage(e),
|
||||
technicalDetails: errorToTechnicalDetails(e),
|
||||
allowRetry: errorAllowsRetry(e),
|
||||
)));
|
||||
},
|
||||
).then((value) {
|
||||
log('Fetch for ${TState.toString()} completed!');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
LoadableState<TState> fromJson(Map<String, dynamic> json) {
|
||||
var rawData = LoadableSaveContext.unwrap(json);
|
||||
return LoadableState(
|
||||
isLoading: true,
|
||||
data: fromStorage(rawData.data),
|
||||
lastFetch: rawData.meta.timestamp,
|
||||
reFetch: null,
|
||||
error: null,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(LoadableState<TState> state) {
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final stateData = state.data;
|
||||
data = stateData is TState ? toStorage(stateData) : null;
|
||||
} catch(e) {
|
||||
log('Failed to save state ${TState.toString()}: ${e.toString()}');
|
||||
}
|
||||
|
||||
return LoadableSaveContext.wrap(
|
||||
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);
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import '../../loadable_state/loading_error.dart';
|
||||
|
||||
class LoadableHydratedBlocEvent<TState> {}
|
||||
class Emit<TState> extends LoadableHydratedBlocEvent<TState> {
|
||||
final TState Function(TState state) state;
|
||||
Emit(this.state);
|
||||
}
|
||||
class DataGathered<TState> extends LoadableHydratedBlocEvent<TState> {
|
||||
final TState Function(TState state) state;
|
||||
DataGathered(this.state);
|
||||
}
|
||||
class Error<TState> extends LoadableHydratedBlocEvent<TState> {
|
||||
final LoadingError error;
|
||||
Error(this.error);
|
||||
}
|
||||
class RefetchStarted<TState> extends LoadableHydratedBlocEvent<TState> {}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'loadable_save_context.freezed.dart';
|
||||
part 'loadable_save_context.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract 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] as Map<String, dynamic>));
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// 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
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
/// @nodoc
|
||||
mixin _$LoadableSaveContext {
|
||||
|
||||
int get timestamp;
|
||||
/// Create a copy of LoadableSaveContext
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$LoadableSaveContextCopyWith<LoadableSaveContext> get copyWith => _$LoadableSaveContextCopyWithImpl<LoadableSaveContext>(this as LoadableSaveContext, _$identity);
|
||||
|
||||
/// Serializes this LoadableSaveContext to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is LoadableSaveContext&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,timestamp);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadableSaveContext(timestamp: $timestamp)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $LoadableSaveContextCopyWith<$Res> {
|
||||
factory $LoadableSaveContextCopyWith(LoadableSaveContext value, $Res Function(LoadableSaveContext) _then) = _$LoadableSaveContextCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
int timestamp
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$LoadableSaveContextCopyWithImpl<$Res>
|
||||
implements $LoadableSaveContextCopyWith<$Res> {
|
||||
_$LoadableSaveContextCopyWithImpl(this._self, this._then);
|
||||
|
||||
final LoadableSaveContext _self;
|
||||
final $Res Function(LoadableSaveContext) _then;
|
||||
|
||||
/// Create a copy of LoadableSaveContext
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? timestamp = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
timestamp: null == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [LoadableSaveContext].
|
||||
extension LoadableSaveContextPatterns on LoadableSaveContext {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _LoadableSaveContext value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _LoadableSaveContext value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _LoadableSaveContext value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int timestamp)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext() when $default != null:
|
||||
return $default(_that.timestamp);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int timestamp) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext():
|
||||
return $default(_that.timestamp);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int timestamp)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _LoadableSaveContext() when $default != null:
|
||||
return $default(_that.timestamp);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _LoadableSaveContext extends LoadableSaveContext {
|
||||
const _LoadableSaveContext({required this.timestamp}): super._();
|
||||
factory _LoadableSaveContext.fromJson(Map<String, dynamic> json) => _$LoadableSaveContextFromJson(json);
|
||||
|
||||
@override final int timestamp;
|
||||
|
||||
/// Create a copy of LoadableSaveContext
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$LoadableSaveContextCopyWith<_LoadableSaveContext> get copyWith => __$LoadableSaveContextCopyWithImpl<_LoadableSaveContext>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$LoadableSaveContextToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoadableSaveContext&&(identical(other.timestamp, timestamp) || other.timestamp == timestamp));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,timestamp);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadableSaveContext(timestamp: $timestamp)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$LoadableSaveContextCopyWith<$Res> implements $LoadableSaveContextCopyWith<$Res> {
|
||||
factory _$LoadableSaveContextCopyWith(_LoadableSaveContext value, $Res Function(_LoadableSaveContext) _then) = __$LoadableSaveContextCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
int timestamp
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$LoadableSaveContextCopyWithImpl<$Res>
|
||||
implements _$LoadableSaveContextCopyWith<$Res> {
|
||||
__$LoadableSaveContextCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _LoadableSaveContext _self;
|
||||
final $Res Function(_LoadableSaveContext) _then;
|
||||
|
||||
/// Create a copy of LoadableSaveContext
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? timestamp = null,}) {
|
||||
return _then(_LoadableSaveContext(
|
||||
timestamp: null == timestamp ? _self.timestamp : timestamp // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'loadable_save_context.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_LoadableSaveContext _$LoadableSaveContextFromJson(Map<String, dynamic> json) =>
|
||||
_LoadableSaveContext(timestamp: (json['timestamp'] as num).toInt());
|
||||
|
||||
Map<String, dynamic> _$LoadableSaveContextToJson(
|
||||
_LoadableSaveContext instance,
|
||||
) => <String, dynamic>{'timestamp': instance.timestamp};
|
||||
Reference in New Issue
Block a user