diff --git a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt index 64dd537..a5e2399 100644 --- a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt +++ b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt @@ -4,6 +4,7 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.media.MediaScannerConnection import android.net.Uri import android.os.Build import android.os.Environment @@ -165,6 +166,14 @@ class MainActivity : FlutterActivity() { Build.VERSION.SDK_INT < Build.VERSION_CODES.O || packageManager.canRequestPackageInstalls() ) "openFolder" -> result.success(openFolder(call.argument("path") ?: "")) + // X-plore 맞추기 (관리자 지시 10/20): .nomedia 를 넣고 지운 뒤 갤러리 · 음악 앱이 다시 읽게 (실패해도 조용히) + "mediaScan" -> result.success(try { + val paths = call.argument>("paths") ?: emptyList() + if (paths.isNotEmpty()) MediaScannerConnection.scanFile(applicationContext, paths.toTypedArray(), null, null) + true + } catch (e: Exception) { + false + }) // 예전 버전으로 되돌리기: Android 는 낮은 버전을 위에 설치하지 못해 앱을 지우는 확인 창을 연다 "uninstallSelf" -> { startActivity(Intent(Intent.ACTION_DELETE, Uri.parse("package:$packageName")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) diff --git a/assets/l10n/en.json b/assets/l10n/en.json index a024a2b..c7fdc4d 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -2744,7 +2744,7 @@ "폴더 수정": "Edit folder", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "Shows the total size of the files inside each folder. Large folders or a NAS can take a while to read. Long-press [Calculate folder size] always works", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "Syncs the folder contents (no same-named folder is created in the target).", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "Files in the folder are not changed. It may take a while to show in the gallery, or you may need to reopen that app.", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "Files in the folder are not changed. It may take a moment to show in the gallery.", "폴더 열기": "Open folder", "폴더 이름": "Folder name", "폴더 이름 바꾸기 · 이동": "Rename · move folder", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 88e057b..a36da9e 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -2744,7 +2744,7 @@ "폴더 수정": "フォルダーを編集", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "フォルダー内のファイルサイズの合計を一覧に表示します。大きなフォルダー・NAS は読み込みに時間がかかることがあります。長押しの [フォルダーサイズを計算] はいつでも使えます", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "フォルダーの中身を合わせます (対象に同じ名前のフォルダーは作りません)。", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "フォルダー内のファイルは変更しません。ギャラリーに反映されるまで時間がかかったり、そのアプリを開き直す必要がある場合があります。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "フォルダー内のファイルは変更しません。ギャラリーに反映されるまで少しかかる場合があります。", "폴더 열기": "フォルダーを開く", "폴더 이름": "フォルダー名", "폴더 이름 바꾸기 · 이동": "フォルダー名の変更・移動", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index 01be808..22d5464 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -2744,7 +2744,7 @@ "폴더 수정": "编辑文件夹", "폴더 안 파일 크기의 합을 목록에 보입니다. 큰 폴더 · NAS 는 읽는 데 오래 걸릴 수 있습니다. 길게 누르기 [폴더 크기 계산] 은 늘 됩니다": "在列表中显示文件夹内文件大小的总和。大型文件夹或 NAS 可能需要较长时间读取。长按 [计算文件夹大小] 始终可用", "폴더 안의 것을 맞춥니다 (대상에 같은 이름의 폴더를 새로 만들지 않음).": "同步文件夹内的内容 (不会在目标中新建同名文件夹)。", - "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.": "不会更改文件夹中的文件。图库可能需要一段时间才能反映,或需要重新打开该应用。", + "폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.": "不会更改文件夹中的文件。图库可能需要片刻才能反映。", "폴더 열기": "打开文件夹", "폴더 이름": "文件夹名称", "폴더 이름 바꾸기 · 이동": "重命名 · 移动文件夹", diff --git a/lib/platform/android/android_shell.dart b/lib/platform/android/android_shell.dart index 091ce31..8f73793 100644 --- a/lib/platform/android/android_shell.dart +++ b/lib/platform/android/android_shell.dart @@ -52,6 +52,15 @@ class AndroidShell extends NoopShell { } catch (_) {} } + /// 미디어 목록 다시 읽기 (MainActivity "mediaScan" - MediaScannerConnection.scanFile). 실패해도 조용히 + @override + Future mediaScan(List paths) async { + if (paths.isEmpty) return; + try { + await _ch.invokeMethod('mediaScan', {'paths': paths}); + } catch (_) {} + } + /// 다른 앱으로 열기 (MainActivity "openWith"): [choose] 면 늘 앱 고르기 창, 아니면 기본 앱 (없으면 고르기 창) @override Future openWith(String path, {bool choose = false}) async { diff --git a/lib/platform/windows/desktop_shell.dart b/lib/platform/windows/desktop_shell.dart index fec6ed2..77fd6a1 100644 --- a/lib/platform/windows/desktop_shell.dart +++ b/lib/platform/windows/desktop_shell.dart @@ -376,6 +376,10 @@ class DesktopShell with WindowListener implements AppShell { @override Future shareFiles(List paths) async => false; + /// Windows 는 갤러리 미디어 목록이 없다 (.nomedia 는 Android 만) + @override + Future mediaScan(List paths) async {} + /// 기본 연결 프로그램 (탐색기에서 두 번 누른 것과 같음) 또는 "연결 프로그램" 고르기 창 @override Future openWith(String path, {bool choose = false}) async { diff --git a/lib/services/app_shell.dart b/lib/services/app_shell.dart index 73bcf18..dd368de 100644 --- a/lib/services/app_shell.dart +++ b/lib/services/app_shell.dart @@ -74,6 +74,10 @@ abstract class AppShell { bool get canShare; Future shareFiles(List paths); + /// X-plore 맞추기 (관리자 지시 10/20): 바뀐 파일 · 폴더를 기기의 미디어 목록에 다시 읽게 한다 (Android MediaScanner - + /// .nomedia 를 넣고 지운 뒤 갤러리에 반영). 다른 기기는 할 일 없음. 실패해도 조용히 넘긴다 + Future mediaScan(List paths); + /// 설치된 외부 브라우저 ('chrome', 'firefox', 'edge', 'whale' 중) List installedBrowsers(); @@ -147,6 +151,8 @@ class NoopShell implements AppShell { @override Future shareFiles(List paths) async => false; @override + Future mediaScan(List paths) async {} + @override List installedBrowsers() => const []; @override Future> missingTools() async => const []; diff --git a/lib/ui/explorer_page.dart b/lib/ui/explorer_page.dart index e7dbbb7..0e62a08 100644 --- a/lib/ui/explorer_page.dart +++ b/lib/ui/explorer_page.dart @@ -4102,7 +4102,7 @@ class _ExplorerPageState extends State { ? tr('이 폴더에 빈 .nomedia 파일을 만듭니다. 갤러리 · 음악 앱이 이 폴더 (안의 폴더 포함) 의 그림 · 동영상 · 음악을 보이지 않게 됩니다.') : tr('이 폴더의 .nomedia 파일을 지웁니다. 갤러리 · 음악 앱이 이 폴더의 그림 · 동영상 · 음악을 다시 보이게 됩니다.')), const SizedBox(height: 6), - Text(tr('폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 시간이 걸리거나 그 앱을 다시 열어야 할 수 있습니다.'), + Text(tr('폴더 안의 파일은 바꾸지 않습니다. 갤러리에 반영되기까지 잠깐 걸릴 수 있습니다.'), style: const TextStyle(fontSize: 12, color: JjColors.textDim)), ]), actions: [ @@ -4123,6 +4123,8 @@ class _ExplorerPageState extends State { _snack(trf('바꾸지 못했습니다: {0}', [err])); return; } + // 관리자 지시 10/20: 갤러리가 바로 알도록 .nomedia 와 그 폴더를 다시 읽게 (넣을 때 · 지울 때 모두, 실패해도 조용히) + await c.services.shell.mediaScan([f.path, dir]); await _refreshAll([dir]); pane.changed(); _snack(on ? tr('갤러리 · 음악 앱에서 숨겼습니다 (.nomedia). 파일은 그대로입니다.') : tr('.nomedia 를 지웠습니다. 갤러리 · 음악 앱에 다시 보입니다.')); diff --git a/test/explorer_test.dart b/test/explorer_test.dart index a5a2f87..96c149c 100644 --- a/test/explorer_test.dart +++ b/test/explorer_test.dart @@ -34,6 +34,9 @@ import 'package:jj_mkvmaker/ui/sync_preview_view.dart' show keepOptionsTogether; /// 다른 앱으로 열기를 기록만 하는 셸 (실제로 프로그램을 띄우지 않게) class _Shell extends NoopShell { final opened = <(String, bool)>[]; + final scanned = >[]; + @override + Future mediaScan(List paths) async => scanned.add(paths); @override Future openWith(String path, {bool choose = false}) async { opened.add((path, choose)); @@ -1767,8 +1770,11 @@ void main() { await act(tester, () => tester.tap(find.text('갤러리 · 음악 앱에서 숨기기 (.nomedia)'))); await calm(tester); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '숨기기'))); - await settle(tester, () => mark.existsSync()); + await settle(tester, () => mark.existsSync() && shell.scanned.isNotEmpty); expect(mark.lengthSync(), 0); + expect(shell.scanned, [ + [mark.path, p.join(left, 'sub')] + ], reason: '갤러리가 다시 읽게 (MediaScanner)'); expect(File(p.join(left, 'sub', 'inner.txt')).readAsStringSync(), 'inner', reason: '안의 파일은 그대로'); // 이제 메뉴는 [다시 보이기] await idle(tester); @@ -1780,6 +1786,8 @@ void main() { expect(find.textContaining('.nomedia 파일을 지웁니다'), findsOneWidget); await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '다시 보이기'))); await settle(tester, () => !mark.existsSync()); + await settle(tester, () => shell.scanned.length == 2); + expect(shell.scanned.last, [mark.path, p.join(left, 'sub')], reason: '지울 때도 다시 읽게'); expect(File(p.join(left, 'sub', 'inner.txt')).existsSync(), isTrue); // 파일에는 없음 await idle(tester);