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 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.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/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/widgets.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:appflowy/util/share_log_files.dart';
|
||||||
import 'package:easy_localization/easy_localization.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 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import 'widgets/widgets.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,10 +59,8 @@ class _ResizableImageState extends State<ResizableImage> {
|
|||||||
|
|
||||||
imageWidth = widget.width;
|
imageWidth = widget.width;
|
||||||
|
|
||||||
if (widget.type == CustomImageType.internal) {
|
|
||||||
_userProfilePB = context.read<DocumentBloc>().state.userProfilePB;
|
_userProfilePB = context.read<DocumentBloc>().state.userProfilePB;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -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);
|
assert(userProfilePB != null && userProfilePB!.token.isNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final manager = CustomImageCacheManager();
|
||||||
|
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
cacheManager: CustomImageCacheManager(),
|
cacheManager: manager,
|
||||||
httpHeaders: _header(),
|
httpHeaders: _header(),
|
||||||
imageUrl: url,
|
imageUrl: url,
|
||||||
fit: fit,
|
fit: fit,
|
||||||
@ -50,6 +52,12 @@ class FlowyNetworkImage extends StatelessWidget {
|
|||||||
errorWidget: (context, url, error) =>
|
errorWidget: (context, url, error) =>
|
||||||
errorWidgetBuilder?.call(context, url, error) ??
|
errorWidgetBuilder?.call(context, url, error) ??
|
||||||
const SizedBox.shrink(),
|
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';
|
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));
|
CustomImageCacheManager._() : super(Config(key));
|
||||||
|
|
||||||
factory CustomImageCacheManager() => _instance;
|
factory CustomImageCacheManager() => _instance;
|
||||||
@ -8,4 +11,16 @@ class CustomImageCacheManager extends CacheManager with ImageCacheManager {
|
|||||||
static final CustomImageCacheManager _instance = CustomImageCacheManager._();
|
static final CustomImageCacheManager _instance = CustomImageCacheManager._();
|
||||||
|
|
||||||
static const key = 'appflowy_image_cache';
|
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/openai/service/openai_client.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_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/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/startup.dart';
|
||||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
||||||
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
|
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
|
||||||
@ -128,6 +130,12 @@ void _resolveCommonService(
|
|||||||
getIt.registerFactory<BaseAppearance>(
|
getIt.registerFactory<BaseAppearance>(
|
||||||
() => PlatformExtension.isMobile ? MobileAppearance() : DesktopAppearance(),
|
() => PlatformExtension.isMobile ? MobileAppearance() : DesktopAppearance(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
getIt.registerFactory<FlowyCacheManager>(
|
||||||
|
() => FlowyCacheManager()
|
||||||
|
..registerCache(TemporaryDirectoryCache())
|
||||||
|
..registerCache(CustomImageCacheManager()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
|
void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import '../../../../generated/locale_keys.g.dart';
|
import '../../../../../generated/locale_keys.g.dart';
|
||||||
|
|
||||||
class SettingsExportFileWidget extends StatefulWidget {
|
class SettingsExportFileWidget extends StatefulWidget {
|
||||||
const SettingsExportFileWidget({
|
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 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.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/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import '../../../../generated/locale_keys.g.dart';
|
import '../../../../../generated/locale_keys.g.dart';
|
||||||
import '../../../../startup/startup.dart';
|
import '../../../../../startup/startup.dart';
|
||||||
import '../../../../startup/tasks/prelude.dart';
|
import '../../../../../startup/tasks/prelude.dart';
|
||||||
|
|
||||||
class SettingsFileLocationCustomizer extends StatefulWidget {
|
class SettingsFileLocationCustomizer extends StatefulWidget {
|
||||||
const SettingsFileLocationCustomizer({
|
const SettingsFileLocationCustomizer({
|
||||||
@ -262,7 +261,7 @@ class _RecoverDefaultStorageButtonState
|
|||||||
tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(),
|
tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(),
|
||||||
icon: const FlowySvg(
|
icon: const FlowySvg(
|
||||||
FlowySvgs.restore_s,
|
FlowySvgs.restore_s,
|
||||||
size: Size.square(24),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// reset to the default directory and reload app
|
// 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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../../../../generated/locale_keys.g.dart';
|
import '../../../../../generated/locale_keys.g.dart';
|
||||||
|
|
||||||
class FileExporterWidget extends StatefulWidget {
|
class FileExporterWidget extends StatefulWidget {
|
||||||
const FileExporterWidget({super.key});
|
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/files/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/files/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/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/foundation.dart';
|
||||||
import 'package:flutter/material.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.
|
// disable export data for v0.2.0 in release mode.
|
||||||
if (kDebugMode) const SettingsExportFileWidget(),
|
if (kDebugMode) const SettingsExportFileWidget(),
|
||||||
const ImportAppFlowyData(),
|
const ImportAppFlowyData(),
|
||||||
|
// clear the cache
|
||||||
|
const SettingsFileCacheWidget(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListView.separated(
|
return SeparatedColumn(
|
||||||
shrinkWrap: true,
|
separatorBuilder: () => const Divider(),
|
||||||
itemBuilder: (context, index) => _items[index],
|
children: _items,
|
||||||
separatorBuilder: (context, index) => const Divider(),
|
|
||||||
itemCount: _items.length,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +457,11 @@
|
|||||||
"recoverLocationTooltips": "Reset to AppFlowy's default data directory",
|
"recoverLocationTooltips": "Reset to AppFlowy's default data directory",
|
||||||
"exportFileSuccess": "Export file successfully!",
|
"exportFileSuccess": "Export file successfully!",
|
||||||
"exportFileFail": "Export file failed!",
|
"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": {
|
"user": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
Loading…
Reference in New Issue
Block a user