mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-26 14:46:19 +00:00
fix: retry count should be clear if the value exceeds max retries (#7195)
* fix: retry count should be clear if the value exceeds max retries * feat: add retry button when loading image failed
This commit is contained in:
parent
0c6d4df14b
commit
fc21d1d245
@ -255,14 +255,17 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
||||
final url = node.attributes[CustomImageBlockKeys.url];
|
||||
return Stack(
|
||||
children: [
|
||||
BlockSelectionContainer(
|
||||
node: node,
|
||||
delegate: this,
|
||||
listenable: editorState.selectionNotifier,
|
||||
cursorColor: editorState.editorStyle.cursorColor,
|
||||
selectionColor: editorState.editorStyle.selectionColor,
|
||||
child: child!,
|
||||
),
|
||||
editorState.editable
|
||||
? BlockSelectionContainer(
|
||||
node: node,
|
||||
delegate: this,
|
||||
listenable: editorState.selectionNotifier,
|
||||
cursorColor: editorState.editorStyle.cursorColor,
|
||||
selectionColor:
|
||||
editorState.editorStyle.selectionColor,
|
||||
child: child!,
|
||||
)
|
||||
: child!,
|
||||
if (value && url.isNotEmpty == true)
|
||||
widget.menuBuilder!(widget.node, this, imageStateNotifier),
|
||||
],
|
||||
|
||||
@ -2,12 +2,14 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -125,6 +127,12 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
return _ImageLoadFailedWidget(
|
||||
width: imageWidth,
|
||||
error: error,
|
||||
onRetry: () {
|
||||
setState(() {
|
||||
final retryCounter = FlowyNetworkRetryCounter();
|
||||
retryCounter.clear(tag: src, url: src);
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -236,19 +244,24 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
}
|
||||
|
||||
class _ImageLoadFailedWidget extends StatelessWidget {
|
||||
const _ImageLoadFailedWidget({required this.width, required this.error});
|
||||
const _ImageLoadFailedWidget({
|
||||
required this.width,
|
||||
required this.error,
|
||||
required this.onRetry,
|
||||
});
|
||||
|
||||
final double width;
|
||||
final Object error;
|
||||
final VoidCallback onRetry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final error = _getErrorMessage();
|
||||
return Container(
|
||||
height: 140,
|
||||
height: 160,
|
||||
width: width,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.6)),
|
||||
@ -258,10 +271,13 @@ class _ImageLoadFailedWidget extends StatelessWidget {
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.broken_image_xl,
|
||||
size: Size.square(48),
|
||||
size: Size.square(36),
|
||||
),
|
||||
FlowyText(AppFlowyEditorL10n.current.imageLoadFailed),
|
||||
const VSpace(6),
|
||||
FlowyText(
|
||||
AppFlowyEditorL10n.current.imageLoadFailed,
|
||||
fontSize: 14,
|
||||
),
|
||||
const VSpace(4),
|
||||
if (error != null)
|
||||
FlowyText(
|
||||
error,
|
||||
@ -270,6 +286,11 @@ class _ImageLoadFailedWidget extends StatelessWidget {
|
||||
fontSize: 10,
|
||||
maxLines: 2,
|
||||
),
|
||||
const VSpace(12),
|
||||
OutlinedRoundedButton(
|
||||
text: LocaleKeys.chat_retry.tr(),
|
||||
onTap: onRetry,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -71,7 +71,7 @@ class FlowyNetworkImage extends StatefulWidget {
|
||||
|
||||
class FlowyNetworkImageState extends State<FlowyNetworkImage> {
|
||||
final manager = CustomImageCacheManager();
|
||||
final retryCounter = _FlowyNetworkRetryCounter();
|
||||
final retryCounter = FlowyNetworkRetryCounter();
|
||||
|
||||
// This is used to clear the retry count when the widget is disposed in case of the url is the same.
|
||||
String? retryTag;
|
||||
@ -104,14 +104,22 @@ class FlowyNetworkImageState extends State<FlowyNetworkImage> {
|
||||
super.reassemble();
|
||||
|
||||
if (retryTag != null) {
|
||||
retryCounter.clear(retryTag!);
|
||||
retryCounter.clear(
|
||||
tag: retryTag!,
|
||||
url: widget.url,
|
||||
maxRetries: widget.maxRetries,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (retryTag != null) {
|
||||
retryCounter.clear(retryTag!);
|
||||
retryCounter.clear(
|
||||
tag: retryTag!,
|
||||
url: widget.url,
|
||||
maxRetries: widget.maxRetries,
|
||||
);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
@ -204,11 +212,12 @@ class FlowyNetworkImageState extends State<FlowyNetworkImage> {
|
||||
}
|
||||
|
||||
/// This class is used to count the number of retries for a given URL.
|
||||
class _FlowyNetworkRetryCounter with ChangeNotifier {
|
||||
_FlowyNetworkRetryCounter._();
|
||||
@visibleForTesting
|
||||
class FlowyNetworkRetryCounter with ChangeNotifier {
|
||||
FlowyNetworkRetryCounter._();
|
||||
|
||||
factory _FlowyNetworkRetryCounter() => _instance;
|
||||
static final _instance = _FlowyNetworkRetryCounter._();
|
||||
factory FlowyNetworkRetryCounter() => _instance;
|
||||
static final _instance = FlowyNetworkRetryCounter._();
|
||||
|
||||
final Map<String, int> _values = <String, int>{};
|
||||
Map<String, int> get values => {..._values};
|
||||
@ -236,9 +245,19 @@ class _FlowyNetworkRetryCounter with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear the retry count for a given URL.
|
||||
void clear(String tag) {
|
||||
/// Clear the retry count for a given tag.
|
||||
void clear({
|
||||
required String tag,
|
||||
required String url,
|
||||
int? maxRetries,
|
||||
}) {
|
||||
_values.remove(tag);
|
||||
|
||||
final retryCount = _values[url];
|
||||
if (maxRetries == null ||
|
||||
(retryCount != null && retryCount >= maxRetries)) {
|
||||
_values.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the retry counter.
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('AppFlowy Network Image:', () {
|
||||
setUpAll(() {
|
||||
Log.shared.disableLog = true;
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
Log.shared.disableLog = false;
|
||||
});
|
||||
|
||||
test(
|
||||
'retry count should be clear if the value exceeds max retries',
|
||||
() async {
|
||||
const maxRetries = 5;
|
||||
const fakeUrl = 'https://plus.unsplash.com/premium_photo-1731948132439';
|
||||
final retryCounter = FlowyNetworkRetryCounter();
|
||||
final tag = retryCounter.add(fakeUrl);
|
||||
for (var i = 0; i < maxRetries; i++) {
|
||||
retryCounter.increment(fakeUrl);
|
||||
expect(retryCounter.getRetryCount(fakeUrl), i + 1);
|
||||
}
|
||||
retryCounter.clear(
|
||||
tag: tag,
|
||||
url: fakeUrl,
|
||||
maxRetries: maxRetries,
|
||||
);
|
||||
expect(retryCounter.getRetryCount(fakeUrl), 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user