mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support clearing caches and fix unable to load image (#4809)
* feat: support clearing caches * fix: try to clear the image cache when loading failed. * feat: clear cache on mobile * chore: add error log
This commit is contained in:
parent
6b05be2362
commit
8944edf75f
@ -1,13 +1,15 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/share_log_files.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import 'widgets/widgets.dart';
|
||||
@ -51,6 +53,31 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_files_clearCache.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
),
|
||||
onTap: () async {
|
||||
await showFlowyMobileConfirmDialog(
|
||||
context,
|
||||
title: FlowyText(
|
||||
LocaleKeys.settings_files_areYouSureToClearCache.tr(),
|
||||
maxLines: 2,
|
||||
),
|
||||
content: FlowyText(
|
||||
LocaleKeys.settings_files_clearCacheDesc.tr(),
|
||||
fontSize: 12,
|
||||
maxLines: 4,
|
||||
),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.error,
|
||||
onActionButtonPressed: () async {
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -59,9 +59,7 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
|
||||
imageWidth = widget.width;
|
||||
|
||||
if (widget.type == CustomImageType.internal) {
|
||||
_userProfilePB = context.read<DocumentBloc>().state.userProfilePB;
|
||||
}
|
||||
_userProfilePB = context.read<DocumentBloc>().state.userProfilePB;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -0,0 +1,61 @@
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class FlowyCacheManager {
|
||||
final _caches = <ICache>[];
|
||||
|
||||
// if you add a new cache, you should register it here.
|
||||
void registerCache(ICache cache) {
|
||||
_caches.add(cache);
|
||||
}
|
||||
|
||||
void unregisterAllCache(ICache cache) {
|
||||
_caches.clear();
|
||||
}
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
for (final cache in _caches) {
|
||||
await cache.clearAll();
|
||||
}
|
||||
|
||||
Log.info('Cache cleared');
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getCacheSize() async {
|
||||
try {
|
||||
int tmpDirSize = 0;
|
||||
for (final cache in _caches) {
|
||||
tmpDirSize += await cache.cacheSize();
|
||||
}
|
||||
Log.info('Cache size: $tmpDirSize');
|
||||
return tmpDirSize;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ICache {
|
||||
Future<int> cacheSize();
|
||||
Future<void> clearAll();
|
||||
}
|
||||
|
||||
class TemporaryDirectoryCache implements ICache {
|
||||
@override
|
||||
Future<int> cacheSize() async {
|
||||
final tmpDir = await getTemporaryDirectory();
|
||||
final tmpDirStat = await tmpDir.stat();
|
||||
return tmpDirStat.size;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearAll() async {
|
||||
final tmpDir = await getTemporaryDirectory();
|
||||
await tmpDir.delete(recursive: true);
|
||||
}
|
||||
}
|
@ -39,8 +39,10 @@ class FlowyNetworkImage extends StatelessWidget {
|
||||
assert(userProfilePB != null && userProfilePB!.token.isNotEmpty);
|
||||
}
|
||||
|
||||
final manager = CustomImageCacheManager();
|
||||
|
||||
return CachedNetworkImage(
|
||||
cacheManager: CustomImageCacheManager(),
|
||||
cacheManager: manager,
|
||||
httpHeaders: _header(),
|
||||
imageUrl: url,
|
||||
fit: fit,
|
||||
@ -50,6 +52,12 @@ class FlowyNetworkImage extends StatelessWidget {
|
||||
errorWidget: (context, url, error) =>
|
||||
errorWidgetBuilder?.call(context, url, error) ??
|
||||
const SizedBox.shrink(),
|
||||
errorListener: (value) {
|
||||
// try to clear the image cache.
|
||||
manager.removeFile(url);
|
||||
|
||||
Log.error(value.toString());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
||||
class CustomImageCacheManager extends CacheManager with ImageCacheManager {
|
||||
class CustomImageCacheManager extends CacheManager
|
||||
with ImageCacheManager
|
||||
implements ICache {
|
||||
CustomImageCacheManager._() : super(Config(key));
|
||||
|
||||
factory CustomImageCacheManager() => _instance;
|
||||
@ -8,4 +11,16 @@ class CustomImageCacheManager extends CacheManager with ImageCacheManager {
|
||||
static final CustomImageCacheManager _instance = CustomImageCacheManager._();
|
||||
|
||||
static const key = 'appflowy_image_cache';
|
||||
|
||||
@override
|
||||
Future<int> cacheSize() async {
|
||||
// https://github.com/Baseflow/flutter_cache_manager/issues/239#issuecomment-719475429
|
||||
// this package does not provide a way to get the cache size
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearAll() async {
|
||||
await emptyCache();
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart';
|
||||
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/shared/custom_image_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
|
||||
@ -128,6 +130,12 @@ void _resolveCommonService(
|
||||
getIt.registerFactory<BaseAppearance>(
|
||||
() => PlatformExtension.isMobile ? MobileAppearance() : DesktopAppearance(),
|
||||
);
|
||||
|
||||
getIt.registerFactory<FlowyCacheManager>(
|
||||
() => FlowyCacheManager()
|
||||
..registerCache(TemporaryDirectoryCache())
|
||||
..registerCache(CustomImageCacheManager()),
|
||||
);
|
||||
}
|
||||
|
||||
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../../../generated/locale_keys.g.dart';
|
||||
import '../../../../../generated/locale_keys.g.dart';
|
||||
|
||||
class SettingsExportFileWidget extends StatefulWidget {
|
||||
const SettingsExportFileWidget({
|
@ -0,0 +1,80 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsFileCacheWidget extends StatelessWidget {
|
||||
const SettingsFileCacheWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_files_clearCache.tr(),
|
||||
fontSize: 13,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const VSpace(8),
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText(
|
||||
LocaleKeys.settings_files_clearCacheDesc.tr(),
|
||||
fontSize: 10,
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const _ClearCacheButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ClearCacheButton extends StatelessWidget {
|
||||
const _ClearCacheButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
tooltipText: LocaleKeys.settings_files_clearCache.tr(),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.delete_s,
|
||||
size: const Size.square(18),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
onPressed: () {
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.settings_files_areYouSureToClearCache.tr(),
|
||||
confirm: () async {
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.settings_files_clearCacheSuccess.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
).show(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
@ -12,12 +9,14 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../../../generated/locale_keys.g.dart';
|
||||
import '../../../../startup/startup.dart';
|
||||
import '../../../../startup/tasks/prelude.dart';
|
||||
import '../../../../../generated/locale_keys.g.dart';
|
||||
import '../../../../../startup/startup.dart';
|
||||
import '../../../../../startup/tasks/prelude.dart';
|
||||
|
||||
class SettingsFileLocationCustomizer extends StatefulWidget {
|
||||
const SettingsFileLocationCustomizer({
|
||||
@ -262,7 +261,7 @@ class _RecoverDefaultStorageButtonState
|
||||
tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.restore_s,
|
||||
size: Size.square(24),
|
||||
size: Size.square(20),
|
||||
),
|
||||
onPressed: () async {
|
||||
// reset to the default directory and reload app
|
@ -18,7 +18,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../../generated/locale_keys.g.dart';
|
||||
import '../../../../../generated/locale_keys.g.dart';
|
||||
|
||||
class FileExporterWidget extends StatefulWidget {
|
||||
const FileExporterWidget({super.key});
|
@ -1,6 +1,8 @@
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_file_import_appflowy_data_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_export_file_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/files/setting_file_import_appflowy_data_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -19,15 +21,15 @@ class _SettingsFileSystemViewState extends State<SettingsFileSystemView> {
|
||||
// disable export data for v0.2.0 in release mode.
|
||||
if (kDebugMode) const SettingsExportFileWidget(),
|
||||
const ImportAppFlowyData(),
|
||||
// clear the cache
|
||||
const SettingsFileCacheWidget(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) => _items[index],
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemCount: _items.length,
|
||||
return SeparatedColumn(
|
||||
separatorBuilder: () => const Divider(),
|
||||
children: _items,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -457,7 +457,11 @@
|
||||
"recoverLocationTooltips": "Reset to AppFlowy's default data directory",
|
||||
"exportFileSuccess": "Export file successfully!",
|
||||
"exportFileFail": "Export file failed!",
|
||||
"export": "Export"
|
||||
"export": "Export",
|
||||
"clearCache": "Clear cache",
|
||||
"clearCacheDesc": "Clear the cache including the images, fonts, and other temporary files. This will not delete your data.",
|
||||
"areYouSureToClearCache": "Are you sure to clear the cache?",
|
||||
"clearCacheSuccess": "Cache cleared successfully!"
|
||||
},
|
||||
"user": {
|
||||
"name": "Name",
|
||||
|
Loading…
Reference in New Issue
Block a user