added timestamp to bloc cache, showing age in offline mode
This commit is contained in:
@ -3,6 +3,7 @@ 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';
|
||||
@ -29,7 +30,6 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
|
||||
bool connectivityStatusKnown() => state.connections != null;
|
||||
bool isConnected() => !(state.connections?.contains(ConnectivityResult.none) ?? true);
|
||||
bool allowRetry() => reFetch != null;
|
||||
bool showErrorMessage() => isConnected() && reFetch != null;
|
||||
|
||||
IconData connectionIcon() => connectivityStatusKnown()
|
||||
? isConnected()
|
||||
@ -37,10 +37,10 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState> {
|
||||
: Icons.signal_wifi_connected_no_internet_4
|
||||
: Icons.device_unknown;
|
||||
|
||||
String connectionText() => connectivityStatusKnown()
|
||||
String connectionText({int? lastUpdated}) => connectivityStatusKnown()
|
||||
? isConnected()
|
||||
? 'Verbindung fehlgeschlagen'
|
||||
: 'Offline'
|
||||
: 'Offline${lastUpdated == null ? '' : ' - Stand von ${Jiffy.parseFromMillisecondsSinceEpoch(lastUpdated).fromNow()}'}'
|
||||
: 'Unbekannte Fehlerursache';
|
||||
|
||||
@override
|
||||
|
@ -11,6 +11,7 @@ class LoadableState<TState> with _$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;
|
||||
|
@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
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;
|
||||
|
||||
@ -35,6 +36,7 @@ abstract class $LoadableStateCopyWith<TState, $Res> {
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
TState? data,
|
||||
int? lastFetch,
|
||||
void Function()? reFetch,
|
||||
LoadingError? error});
|
||||
|
||||
@ -57,6 +59,7 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? data = freezed,
|
||||
Object? lastFetch = freezed,
|
||||
Object? reFetch = freezed,
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
@ -69,6 +72,10 @@ class _$LoadableStateCopyWithImpl<TState, $Res,
|
||||
? _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
|
||||
@ -104,6 +111,7 @@ abstract class _$$LoadableStateImplCopyWith<TState, $Res>
|
||||
$Res call(
|
||||
{bool isLoading,
|
||||
TState? data,
|
||||
int? lastFetch,
|
||||
void Function()? reFetch,
|
||||
LoadingError? error});
|
||||
|
||||
@ -125,6 +133,7 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||
$Res call({
|
||||
Object? isLoading = null,
|
||||
Object? data = freezed,
|
||||
Object? lastFetch = freezed,
|
||||
Object? reFetch = freezed,
|
||||
Object? error = freezed,
|
||||
}) {
|
||||
@ -137,6 +146,10 @@ class __$$LoadableStateImplCopyWithImpl<TState, $Res>
|
||||
? _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
|
||||
@ -155,6 +168,7 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
const _$LoadableStateImpl(
|
||||
{this.isLoading = true,
|
||||
this.data = null,
|
||||
this.lastFetch = null,
|
||||
this.reFetch = null,
|
||||
this.error = null})
|
||||
: super._();
|
||||
@ -167,6 +181,9 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
final TState? data;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int? lastFetch;
|
||||
@override
|
||||
@JsonKey()
|
||||
final void Function()? reFetch;
|
||||
@override
|
||||
@JsonKey()
|
||||
@ -174,7 +191,7 @@ class _$LoadableStateImpl<TState> extends _LoadableState<TState> {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, reFetch: $reFetch, error: $error)';
|
||||
return 'LoadableState<$TState>(isLoading: $isLoading, data: $data, lastFetch: $lastFetch, reFetch: $reFetch, error: $error)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -185,13 +202,15 @@ class _$LoadableStateImpl<TState> extends _LoadableState<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), reFetch, error);
|
||||
const DeepCollectionEquality().hash(data), lastFetch, reFetch, error);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@ -205,6 +224,7 @@ 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._();
|
||||
@ -214,6 +234,8 @@ abstract class _LoadableState<TState> extends LoadableState<TState> {
|
||||
@override
|
||||
TState? get data;
|
||||
@override
|
||||
int? get lastFetch;
|
||||
@override
|
||||
void Function()? get reFetch;
|
||||
@override
|
||||
LoadingError? get error;
|
||||
|
@ -53,13 +53,13 @@ class LoadableStateConsumer<TController extends Bloc<LoadableHydratedBlocEvent<T
|
||||
bloc.reFetch = loadableState.reFetch;
|
||||
return Column(
|
||||
children: [
|
||||
LoadableStateErrorBar(visible: loadableState.showErrorBar()),
|
||||
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()),
|
||||
LoadableStateErrorScreen(visible: loadableState.showError(), message: loadableState.error?.message),
|
||||
|
||||
AnimatedOpacity(
|
||||
opacity: loadableState.showContent() ? 1.0 : 0.0,
|
||||
|
@ -1,11 +1,14 @@
|
||||
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;
|
||||
const LoadableStateErrorBar({required this.visible, super.key});
|
||||
final String? message;
|
||||
final int? lastUpdated;
|
||||
const LoadableStateErrorBar({required this.visible, this.message, this.lastUpdated, super.key});
|
||||
|
||||
final Duration animationDuration = const Duration(milliseconds: 200);
|
||||
|
||||
@ -29,25 +32,31 @@ class LoadableStateErrorBar extends StatelessWidget {
|
||||
builder: (context) {
|
||||
var bloc = context.watch<LoadableStateBloc>();
|
||||
var status = (
|
||||
icon: bloc.connectionIcon(),
|
||||
text: bloc.connectionText(),
|
||||
color: bloc.connectivityStatusKnown() && !bloc.isConnected()
|
||||
icon: bloc.connectionIcon(),
|
||||
text: bloc.connectionText(lastUpdated: lastUpdated),
|
||||
color: bloc.connectivityStatusKnown() && !bloc.isConnected()
|
||||
? Colors.grey.shade600
|
||||
: Theme.of(context).primaryColor
|
||||
);
|
||||
|
||||
return Container(
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: status.color,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(status.icon, size: 14),
|
||||
const SizedBox(width: 10),
|
||||
Text(status.text, style: const TextStyle(fontSize: 12))
|
||||
],
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if(!bloc.isConnected()) return;
|
||||
InfoDialog.show(context, 'Exception: ${message.toString()}');
|
||||
},
|
||||
child: Container(
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: status.color,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(status.icon, size: 14),
|
||||
const SizedBox(width: 10),
|
||||
Text(status.text, style: const TextStyle(fontSize: 12))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -6,7 +6,8 @@ import 'loadable_state_consumer.dart';
|
||||
|
||||
class LoadableStateErrorScreen extends StatelessWidget {
|
||||
final bool visible;
|
||||
const LoadableStateErrorScreen({required this.visible, super.key});
|
||||
final String? message;
|
||||
const LoadableStateErrorScreen({required this.visible, this.message, super.key});
|
||||
|
||||
|
||||
@override
|
||||
@ -28,11 +29,19 @@ class LoadableStateErrorScreen extends StatelessWidget {
|
||||
if(bloc.allowRetry()) ...[
|
||||
const SizedBox(height: 10),
|
||||
TextButton(onPressed: () => bloc.reFetch!(), child: const Text('Erneut versuschen')),
|
||||
],
|
||||
|
||||
if(bloc.showErrorMessage()) ...[
|
||||
const SizedBox(height: 40),
|
||||
Text("bloc.loadingError!.message", style: TextStyle(color: Theme.of(context).hintColor, fontSize: 12))
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
Reference in New Issue
Block a user