폴더 열기 · 결과 폴더 버튼 · 웹 브라우저 버튼 위치

- Android [폴더 열기]: 다른 파일 관리자 (X-plore 등) 가 열려 폴더로 가지 않던 것 → 삼성 내 파일 (그 폴더로 바로) · 시스템 파일 앱 순으로
- MKV 화면: MKV 만들기 옆에 [결과 폴더] (보고 있는 동영상의 jj_mkv 폴더 열기, 아직 없으면 알림)
- 웹 브라우저 버튼: MKV 만들기 옆 → 위쪽 왼쪽 공통 버튼 (MKV 화면 다음, 뒤로 앞). 모든 화면 같은 자리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
RyuJongJin 2026-10-03 02:15:04 +09:00
parent 4cf2ac29ee
commit 7a34bedbb6
4 changed files with 58 additions and 19 deletions

View File

@ -59,13 +59,27 @@ class MainActivity : FlutterActivity() {
val root = Environment.getExternalStorageDirectory().path val root = Environment.getExternalStorageDirectory().path
if (!path.startsWith(root)) return false if (!path.startsWith(root)) return false
val rel = path.removePrefix(root).trim('/') val rel = path.removePrefix(root).trim('/')
// 1. 삼성 "내 파일": 그 폴더로 바로 연다
try {
startActivity(
Intent("samsung.myfiles.intent.action.LAUNCH_MY_FILES")
.setPackage("com.sec.android.app.myfiles")
.putExtra("samsung.myfiles.intent.extra.START_PATH", path)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
return true
} catch (e: Exception) {
}
// 2. 시스템 "파일" 앱 (Google / AOSP). 다른 파일 관리자 (X-plore 등) 는 이 주소의 폴더로 가지 않아 앱을 정해 연다
val uri = android.provider.DocumentsContract.buildDocumentUri( val uri = android.provider.DocumentsContract.buildDocumentUri(
"com.android.externalstorage.documents", "primary:$rel" "com.android.externalstorage.documents", "primary:$rel"
) )
for (type in listOf(android.provider.DocumentsContract.Document.MIME_TYPE_DIR, "resource/folder")) { for (pkg in listOf("com.google.android.documentsui", "com.android.documentsui", null)) {
try { try {
startActivity( startActivity(
Intent(Intent.ACTION_VIEW).setDataAndType(uri, type) Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, android.provider.DocumentsContract.Document.MIME_TYPE_DIR)
.apply { if (pkg != null) setPackage(pkg) }
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
) )
return true return true

View File

@ -41,9 +41,12 @@ class AppScope extends InheritedWidget {
class AppNavButtons extends StatelessWidget { class AppNavButtons extends StatelessWidget {
/// 다운로드 목록 화면 자신 (그 버튼을 "지금 여기" 로 표시) /// 다운로드 목록 화면 자신 (그 버튼을 "지금 여기" 로 표시)
final bool onDownloadsPage; final bool onDownloadsPage;
const AppNavButtons({super.key, this.onDownloadsPage = false});
static const double width = 4 * 40; /// 웹 브라우저 화면 자신
final bool onBrowserPage;
const AppNavButtons({super.key, this.onDownloadsPage = false, this.onBrowserPage = false});
static const double width = 5 * 40;
/// MKV 화면 (맨 처음 화면) 까지 돌아가기 /// MKV 화면 (맨 처음 화면) 까지 돌아가기
static void toMkv(BuildContext context) => Navigator.of(context).popUntil((r) => r.isFirst); static void toMkv(BuildContext context) => Navigator.of(context).popUntil((r) => r.isFirst);
@ -91,6 +94,19 @@ class AppNavButtons extends StatelessWidget {
atRoot ? null : () => toMkv(context), atRoot ? null : () => toMkv(context),
here: atRoot, here: atRoot,
), ),
// 웹 브라우저: MKV 화면 버튼 다음 (모든 화면 같은 자리)
btn(
Icon(Icons.public, color: onBrowserPage ? JjColors.accent : null),
onBrowserPage ? '웹 브라우저 (지금 여기)' : '웹 브라우저',
scope?.bookmarks == null || onBrowserPage
? null
: () => Navigator.push(
context,
MaterialPageRoute<void>(
builder: (_) =>
BrowserPage(c: scope!.controller, downloads: scope.downloads, bookmarks: scope.bookmarks!))),
here: onBrowserPage,
),
btn(const Icon(Icons.arrow_back), '뒤로', atRoot ? null : () => Navigator.maybePop(context)), btn(const Icon(Icons.arrow_back), '뒤로', atRoot ? null : () => Navigator.maybePop(context)),
btn( btn(
Icon(Icons.download_for_offline_outlined, color: onDownloadsPage ? JjColors.accent : null), Icon(Icons.download_for_offline_outlined, color: onDownloadsPage ? JjColors.accent : null),

View File

@ -274,7 +274,7 @@ class _BrowserPageState extends State<BrowserPage> {
color: JjColors.panel, color: JjColors.panel,
padding: const EdgeInsets.only(left: 8, right: appBarRightPadding), padding: const EdgeInsets.only(left: 8, right: appBarRightPadding),
child: Row(children: [ child: Row(children: [
const AppNavButtons(), const AppNavButtons(onBrowserPage: true),
const SizedBox(width: 8), const SizedBox(width: 8),
Container(width: 1, height: 24, color: JjColors.border), Container(width: 1, height: 24, color: JjColors.border),
const SizedBox(width: 4), const SizedBox(width: 4),

View File

@ -12,7 +12,6 @@ import '../core/models.dart';
import '../core/output_paths.dart'; import '../core/output_paths.dart';
import 'ai_dialog.dart'; import 'ai_dialog.dart';
import 'app_actions.dart'; import 'app_actions.dart';
import 'browser_page.dart';
import 'downloads_page.dart'; import 'downloads_page.dart';
import 'player_page.dart'; import 'player_page.dart';
import 'subtitle_editor_page.dart'; import 'subtitle_editor_page.dart';
@ -95,6 +94,21 @@ class _TopBar extends StatelessWidget {
const _TopBar({required this.c, this.downloads, this.onExit, this.bookmarks}); const _TopBar({required this.c, this.downloads, this.onExit, this.bookmarks});
/// 고른 동영상이 없을 때: 목록 전체를 만들지 묻는다 /// 고른 동영상이 없을 때: 목록 전체를 만들지 묻는다
/// 보고 있는 동영상의 결과 폴더 (jj_mkv) 를 탐색기 · 파일 앱으로. 아직 없으면 알린다.
Future<void> _openOutput(BuildContext context) async {
final v = c.selected;
if (v == null) return;
final dir = outputDirFor(v.path);
if (!await Directory(dir).exists()) {
if (context.mounted) {
ScaffoldMessenger.maybeOf(context)
?.showSnackBar(SnackBar(content: Text('아직 만든 MKV · 자막이 없습니다: $dir')));
}
return;
}
await c.services.shell.revealFile(dir);
}
Future<void> _confirmBuildAll(BuildContext context) async { Future<void> _confirmBuildAll(BuildContext context) async {
final todo = c.videos.where((v) => v.status != JobStatus.done).length; final todo = c.videos.where((v) => v.status != JobStatus.done).length;
final ok = await showDialog<bool>( final ok = await showDialog<bool>(
@ -139,7 +153,7 @@ class _TopBar extends StatelessWidget {
(c.busy ? (jobText ? 350 : 60) : 0) + (c.busy ? (jobText ? 350 : 60) : 0) +
(hasPlayer ? (rightText ? 201 : 48) : 0) + (hasPlayer ? (rightText ? 201 : 48) : 0) +
(mkvText ? 166 + (c.busy && jobText ? 60 : 0) : 48) + (mkvText ? 166 + (c.busy && jobText ? 60 : 0) : 48) +
(bookmarks != null ? (rightText ? 138 : 48) : 0) + (rightText ? 166 : 48) + // 결과 폴더
(dlBox ? 162 : 0) + (dlBox ? 162 : 0) +
(usage ? 220 : 0) + 96 + 92 + (usage ? 220 : 0) + 96 + 92 +
(c.checked.isEmpty ? 0 : 3 * 26) + (c.checked.isEmpty ? 0 : 3 * 26) +
@ -241,18 +255,13 @@ class _TopBar extends StatelessWidget {
icon: Icon(c.busy ? Icons.playlist_add : Icons.play_arrow, size: 20), icon: Icon(c.busy ? Icons.playlist_add : Icons.play_arrow, size: 20),
), ),
), ),
if (bookmarks != null) ...[ // 만든 MKV 가 있는 폴더 (jj_mkv). 웹 브라우저 버튼은 왼쪽 공통 버튼으로 옮김
const SizedBox(width: 8), const SizedBox(width: 8),
action( action(Icons.folder_special_outlined, '결과 폴더', c.selected == null ? null : () => _openOutput(context),
Icons.public, text: rightText,
'브라우저', tip: c.selected == null
() => Navigator.push( ? '결과 폴더 열기 (동영상을 고르세요)'
context, : '결과 폴더 열기: ${outputDirFor(c.selected!.path)}'),
MaterialPageRoute<void>(
builder: (_) => BrowserPage(c: c, downloads: downloads, bookmarks: bookmarks!))),
text: rightText,
tip: '웹 브라우저'),
],
if (dlBox) ...[ if (dlBox) ...[
const SizedBox(width: 12), const SizedBox(width: 12),
_DownloadBox(d: downloads!), _DownloadBox(d: downloads!),