From dc10aa89dd61eb1f40429fe7cf0c74c37df29307 Mon Sep 17 00:00:00 2001 From: tacko-o <140358540+tacko-o@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:33:47 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=82=AF=E3=83=AA=E3=82=A2=E6=A9=9F=E8=83=BD=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全般設定にキャッシュクリア機能を追加 キャッシュクリアに関するl18n関連の文言追加 --- lib/l10n/app_ja-oj.arb | 3 + lib/l10n/app_ja.arb | 3 + lib/l10n/app_zh-cn.arb | 2 + lib/l10n/app_zh.arb | 2 + lib/util/cache_size.dart | 71 +++++++++++++++++++ .../general_settings_page.dart | 36 ++++++++++ 6 files changed, 117 insertions(+) create mode 100644 lib/util/cache_size.dart diff --git a/lib/l10n/app_ja-oj.arb b/lib/l10n/app_ja-oj.arb index bac68a8d4..5e929fe69 100644 --- a/lib/l10n/app_ja-oj.arb +++ b/lib/l10n/app_ja-oj.arb @@ -106,6 +106,9 @@ "cannotMentionToRemoteInLocalOnlyNote": "連合切られているのに他のサーバーの人がメンションに含まれているようですわ", "cannotPublicReplyToPrivateNote": "リプライが{visibility}のようでして……パブリックにはできませんこと", + "cache": "キャッシュ", + "clearCache": "キャッシュとお別れいたしますわ", + "unsupportedFile": "対応してないファイルのようですわ", "failedFileSave": "ファイルの保存に失敗したようですわね…" diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 55369c6e0..b234cd00b 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -826,6 +826,9 @@ "fontSize": "フォントサイズ", "systemFont": "システム標準", + "cache": "キャッシュ", + "clearCache": "キャッシュをクリア", + "selectFolder": "フォルダー選択", "settingsFileManagement": "設定ファイルの管理", "importAndExportSettingsDescription": "現在の設定から、アカウントのログイン情報を除くすべての設定を設定ファイルに出力して管理することができます。設定ファイルは、指定したアカウントの「ドライブ」内に保存されます。", diff --git a/lib/l10n/app_zh-cn.arb b/lib/l10n/app_zh-cn.arb index 0e7bda852..6c6572212 100644 --- a/lib/l10n/app_zh-cn.arb +++ b/lib/l10n/app_zh-cn.arb @@ -773,6 +773,8 @@ "fontFantasy": "字体 (适用于 $[font.fantasy )", "fontSize": "字体大小", "systemFont": "系统标准", + "cache": "快取", + "clearCache": "清除快取", "selectFolder": "选择文件夹", "importSettings": "导入设置", "importSettingsDescription": "从驱动器加载配置文件。配置文件保存时会记录所有账号的配置信息,但只会加载登录本设备的账号的信息。", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 0e7bda852..6c6572212 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -773,6 +773,8 @@ "fontFantasy": "字体 (适用于 $[font.fantasy )", "fontSize": "字体大小", "systemFont": "系统标准", + "cache": "快取", + "clearCache": "清除快取", "selectFolder": "选择文件夹", "importSettings": "导入设置", "importSettingsDescription": "从驱动器加载配置文件。配置文件保存时会记录所有账号的配置信息,但只会加载登录本设备的账号的信息。", diff --git a/lib/util/cache_size.dart b/lib/util/cache_size.dart new file mode 100644 index 000000000..4009cd023 --- /dev/null +++ b/lib/util/cache_size.dart @@ -0,0 +1,71 @@ +import "dart:io"; +import "dart:math"; +import "package:flutter/material.dart"; +import "package:path_provider/path_provider.dart"; + +Future getCacheSizeWithUnit() async { + const unitArr = ["Byte", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + // キャッシュサイズ + final cacheSizeByte = await getCacheSizeByte(); + + // 単位 + final unitIndex = (cacheSizeByte.toString().length / 3).ceil() - 1; + final unit = unitArr[unitIndex]; + + // "00.0 GB"の形にする + final cacheSizeStr = (cacheSizeByte / pow(1000, unitIndex)).toStringAsFixed(1); + return "$cacheSizeStr $unit"; +} + +/// キャッシュサイズを取得する +Future getCacheSizeByte() async { + // キャッシュ格納ディレクトリを取得して + // 中身のファイルサイズを全て合計 + final tempDir = await getTemporaryDirectory(); + final tempDirSize = _getFileDirSize(tempDir); + return tempDirSize; +} + +/// ファイルサイズを取得する +int _getFileDirSize(FileSystemEntity file) { + if (file is File) { + // ファイルならファイルサイズを返却 + return file.lengthSync(); + } else if (file is Directory) { + // ディレクトリなら配下のファイルサイズの合計を返す + var sum = 0; + final children = file.listSync(); + for (final child in children) { + sum += _getFileDirSize(child); + } + return sum; + } + return 0; +} + +/// キャッシュをクリアする +Future clearCache() async { + // キャッシュ格納ディレクトリを取得して + // 配下のファイルを全て削除 + final tempDir = await getTemporaryDirectory(); + await deleteFile(tempDir); + final cacheSizeStr = await getCacheSizeWithUnit(); + + // キャッシュクリア後のキャッシュサイズを返す(0byteのはず) + return cacheSizeStr; +} + +/// ファイルを削除する +Future deleteFile(FileSystemEntity file) async { + if (file is File) { + // ファイならそのまま削除 + file.deleteSync(); + } else if (file is Directory) { + // ディレクトリなら配下のファイルを再起的に削除 + final children = file.listSync(); + for (final child in children) { + await deleteFile(child); + } + } +} diff --git a/lib/view/settings_page/general_settings_page/general_settings_page.dart b/lib/view/settings_page/general_settings_page/general_settings_page.dart index b71a61677..e4542d730 100644 --- a/lib/view/settings_page/general_settings_page/general_settings_page.dart +++ b/lib/view/settings_page/general_settings_page/general_settings_page.dart @@ -9,6 +9,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:miria/const.dart"; import "package:miria/model/general_settings.dart"; import "package:miria/providers.dart"; +import "package:miria/util/cache_size.dart"; import "package:miria/view/themes/built_in_color_themes.dart"; @RoutePage() @@ -39,6 +40,7 @@ class GeneralSettingsPage extends HookConsumerWidget { final fantasyFontName = useState(settings.fantasyFontName); final language = useState(settings.languages); final isDeckMode = useState(settings.isDeckMode); + final cacheSize = useState(""); useMemoized(() { if (lightModeTheme.value.isEmpty) { @@ -109,6 +111,13 @@ class GeneralSettingsPage extends HookConsumerWidget { useMemoized(() => unawaited(save()), dependencies); + useEffect(() { + unawaited(getCacheSizeWithUnit().then((value) => + cacheSize.value = value, + ),); + return null; + }, [], ); + return Scaffold( appBar: AppBar(title: Text(S.of(context).generalSettings)), body: SingleChildScrollView( @@ -487,6 +496,33 @@ class GeneralSettingsPage extends HookConsumerWidget { ), ), ), + Card( + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).cache, + style: Theme.of(context).textTheme.titleLarge, + ), + ListTile( + title: Text(cacheSize.value), + trailing: ElevatedButton( + onPressed: () async { + await clearCache().then((value) => + cacheSize.value = value, + ); + }, + child: Text(S.of(context).clearCache), + ), + ), + ], + ), + ), + ), ], ), ), From 29d0c5a9822fa9e2d7e2a146bbb08da3ae103920 Mon Sep 17 00:00:00 2001 From: tacko-o <140358540+tacko-o@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:00:18 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit コメント脱字修正 --- lib/util/cache_size.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/cache_size.dart b/lib/util/cache_size.dart index 4009cd023..dc5b23e62 100644 --- a/lib/util/cache_size.dart +++ b/lib/util/cache_size.dart @@ -59,7 +59,7 @@ Future clearCache() async { /// ファイルを削除する Future deleteFile(FileSystemEntity file) async { if (file is File) { - // ファイならそのまま削除 + // ファイルならそのまま削除 file.deleteSync(); } else if (file is Directory) { // ディレクトリなら配下のファイルを再起的に削除 From 49d6fc112a0653b78dd0e3151555f9c027b9c14a Mon Sep 17 00:00:00 2001 From: tacko-o <140358540+tacko-o@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:24:28 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AAimport=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不要なimport削除 --- lib/util/cache_size.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/util/cache_size.dart b/lib/util/cache_size.dart index dc5b23e62..ee721eed2 100644 --- a/lib/util/cache_size.dart +++ b/lib/util/cache_size.dart @@ -1,6 +1,5 @@ import "dart:io"; import "dart:math"; -import "package:flutter/material.dart"; import "package:path_provider/path_provider.dart"; Future getCacheSizeWithUnit() async { From a507b5d67b2005ba1b66b8d295671023f87d3732 Mon Sep 17 00:00:00 2001 From: tacko-o <140358540+tacko-o@users.noreply.github.com> Date: Sun, 12 Jan 2025 17:53:51 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A5=E3=81=AE=E5=8F=96=E5=BE=97=E5=89=8A=E9=99=A4=E3=82=92?= =?UTF-8?q?=E9=9D=9E=E5=90=8C=E6=9C=9F=E3=81=AB=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit キャッシュされたファイルが多いとキャッシュサイズ取得中にUIが固まってしまうため、キャッシュサイズ取得を非同期で行うように変更 同じ理由でキャッシュクリア時も非同期で行うように変更 getTemporaryDirectory()はLinuxだと/tmp/が返却されてしまうので、getTemporaryDirectory()配下のファイルをすべて削除するのではなくlibCachedImageData/のみを削除するように変更 --- lib/util/cache_size.dart | 82 +++++++++---------- .../general_settings_page.dart | 33 +++++--- 2 files changed, 62 insertions(+), 53 deletions(-) diff --git a/lib/util/cache_size.dart b/lib/util/cache_size.dart index ee721eed2..1eb543e81 100644 --- a/lib/util/cache_size.dart +++ b/lib/util/cache_size.dart @@ -2,69 +2,67 @@ import "dart:io"; import "dart:math"; import "package:path_provider/path_provider.dart"; +/// 単位付きのキャッシュサイズを取得する Future getCacheSizeWithUnit() async { const unitArr = ["Byte", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; // キャッシュサイズ - final cacheSizeByte = await getCacheSizeByte(); - + final cacheSizeByte = await _getCacheSizeByte(); + // 単位 final unitIndex = (cacheSizeByte.toString().length / 3).ceil() - 1; final unit = unitArr[unitIndex]; // "00.0 GB"の形にする final cacheSizeStr = (cacheSizeByte / pow(1000, unitIndex)).toStringAsFixed(1); + return "$cacheSizeStr $unit"; } +/// キャッシュをクリアする +Future clearCache() async { + // 画像キャッシュ格納ディレクトリを削除 + final cacheDir = await _getLibCachedImageDataDir(); + if (await cacheDir.exists()){ + // ディレクトリが存在する場合のみ実行(ボタン連打対策) + await cacheDir.delete(recursive: true); + } + final cacheSizeStr = await getCacheSizeWithUnit(); + + // キャッシュクリア後のキャッシュサイズを返す("0.0 Byte"のはず) + return cacheSizeStr; +} + /// キャッシュサイズを取得する -Future getCacheSizeByte() async { +Future _getCacheSizeByte() async { // キャッシュ格納ディレクトリを取得して // 中身のファイルサイズを全て合計 - final tempDir = await getTemporaryDirectory(); - final tempDirSize = _getFileDirSize(tempDir); - return tempDirSize; + final cacheDir = await _getLibCachedImageDataDir(); + final cacheSize = await _getDirSize(cacheDir); + + return cacheSize; } -/// ファイルサイズを取得する -int _getFileDirSize(FileSystemEntity file) { - if (file is File) { - // ファイルならファイルサイズを返却 - return file.lengthSync(); - } else if (file is Directory) { - // ディレクトリなら配下のファイルサイズの合計を返す - var sum = 0; - final children = file.listSync(); - for (final child in children) { - sum += _getFileDirSize(child); - } - return sum; +/// ディレクトリ配下のファイルサイズ合計を取得する +Future _getDirSize(Directory dir) async { + // ディレクトリが存在しない場合は0を返却 + if (!(await dir.exists())) { + return 0; } - return 0; -} -/// キャッシュをクリアする -Future clearCache() async { - // キャッシュ格納ディレクトリを取得して - // 配下のファイルを全て削除 - final tempDir = await getTemporaryDirectory(); - await deleteFile(tempDir); - final cacheSizeStr = await getCacheSizeWithUnit(); + // dir配下のファイル・ディレクトリをすべて取得してファイルサイズの合計を取得 + final dirSize = dir + .list(recursive: true) + .fold(0, (prev, element) => prev + element.statSync().size); - // キャッシュクリア後のキャッシュサイズを返す(0byteのはず) - return cacheSizeStr; + return dirSize; } -/// ファイルを削除する -Future deleteFile(FileSystemEntity file) async { - if (file is File) { - // ファイルならそのまま削除 - file.deleteSync(); - } else if (file is Directory) { - // ディレクトリなら配下のファイルを再起的に削除 - final children = file.listSync(); - for (final child in children) { - await deleteFile(child); - } - } +/// libCachedImageDataのパスを取得する +Future _getLibCachedImageDataDir() async { + const libCachedImageDataPath = "libCachedImageData"; + final tempDir = await getTemporaryDirectory(); + final libCachedImageDataDir = Directory("${tempDir.path}/$libCachedImageDataPath"); + + return libCachedImageDataDir; } diff --git a/lib/view/settings_page/general_settings_page/general_settings_page.dart b/lib/view/settings_page/general_settings_page/general_settings_page.dart index e4542d730..db150a6fe 100644 --- a/lib/view/settings_page/general_settings_page/general_settings_page.dart +++ b/lib/view/settings_page/general_settings_page/general_settings_page.dart @@ -1,4 +1,5 @@ import "dart:async"; +import "dart:io"; import "package:auto_route/auto_route.dart"; import "package:collection/collection.dart"; @@ -111,13 +112,17 @@ class GeneralSettingsPage extends HookConsumerWidget { useMemoized(() => unawaited(save()), dependencies); + // キャッシュサイズ表示 + final getCacheSize = useMemoized(() => (){ + WidgetsBinding.instance.addPostFrameCallback((_) async { + cacheSize.value = await getCacheSizeWithUnit(); + }); + },); useEffect(() { - unawaited(getCacheSizeWithUnit().then((value) => - cacheSize.value = value, - ),); + getCacheSize(); return null; - }, [], ); - + }, [],); + return Scaffold( appBar: AppBar(title: Text(S.of(context).generalSettings)), body: SingleChildScrollView( @@ -509,13 +514,19 @@ class GeneralSettingsPage extends HookConsumerWidget { style: Theme.of(context).textTheme.titleLarge, ), ListTile( - title: Text(cacheSize.value), + title: (cacheSize.value != "") + ? Text(cacheSize.value) + : const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [CircularProgressIndicator()], + ), trailing: ElevatedButton( - onPressed: () async { - await clearCache().then((value) => - cacheSize.value = value, - ); - }, + onPressed: (cacheSize.value != "") + ? () async { + cacheSize.value = ""; + cacheSize.value = await clearCache(); + } + : null, child: Text(S.of(context).clearCache), ), ),