diff --git a/lib/app/settings.dart b/lib/app/settings.dart index 84e7623..b2c09ab 100644 --- a/lib/app/settings.dart +++ b/lib/app/settings.dart @@ -74,6 +74,9 @@ class AppSettings { /// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가 String openFileAction = 'play'; + /// Android 화면 방향: 'landscape' 가로 고정 (기본) / 'portrait' 세로 고정 / 'auto' 기기 방향 따라 + String screenOrientation = 'landscape'; + /// 프로그램이 켜져 있을 때 탐색기에서 연 동영상 재생: 'same' 켜져 있는 창에서 / 'new' 새 재생 창 String openFileWindow = 'same'; String homeUrl = 'https://www.youtube.com/'; @@ -134,6 +137,7 @@ class AppSettings { 'uiScale': uiScale, 'uiScaleDefault': uiScaleDefault, 'openFileAction': openFileAction, + 'screenOrientation': screenOrientation, 'openFileWindow': openFileWindow, 'homeUrl': homeUrl, 'browserEngine': browserEngine, @@ -181,6 +185,9 @@ class AppSettings { ..uiScale = clampUiScale(j['uiScale'] as num?) ..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?) ..openFileAction = j['openFileAction'] == 'add' ? 'add' : 'play' + ..screenOrientation = const ['portrait', 'auto'].contains(j['screenOrientation']) + ? j['screenOrientation'] as String + : 'landscape' ..openFileWindow = j['openFileWindow'] == 'new' ? 'new' : 'same' ..homeUrl = j['homeUrl'] as String? ?? 'https://www.youtube.com/' ..browserEngine = j['browserEngine'] as String? ?? 'edge' diff --git a/lib/main.dart b/lib/main.dart index 9540ed2..4533377 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,6 +16,7 @@ import 'core/app_update.dart'; import 'core/playlist.dart'; import 'platform/android/android_download_tools.dart'; import 'platform/android/android_keep_alive.dart'; +import 'platform/android/android_shell.dart' show applyScreenOrientation; import 'platform/android/android_storage.dart'; import 'platform/windows/app_paths.dart'; import 'platform/windows/cef_runtime.dart'; @@ -354,15 +355,15 @@ String lastWorkText(AppController c) { } /// Android: 창 하나. 트레이 · 단축키 · 탐색기 연결 · 다운로드 (yt-dlp · aria2) · 업데이트는 없다. -/// 데스크톱 화면을 그대로 쓰므로 가로 화면으로 띄운다. +/// 데스크톱 화면을 그대로 쓰므로 기본은 가로 화면 (환경 설정 > 화면 방향에서 세로 · 자동으로 바꿀 수 있음). Future runAndroid(String dataDir) async { - await SystemChrome.setPreferredOrientations( - [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); + await applyScreenOrientation('landscape'); final services = PlatformServices.create(); final logs = Directory(p.join(dataDir, 'Logs'))..createSync(recursive: true); final controller = AppController(services, settingsStore: SettingsStore())..logFile = p.join(logs.path, 'app.log'); controller.note('── 시작 $appTitle (Android) ──'); await controller.init(); + await applyScreenOrientation(controller.settings.screenOrientation); final bookmarks = BookmarksController(); await bookmarks.load(); final navigatorKey = GlobalKey(); diff --git a/lib/platform/android/android_shell.dart b/lib/platform/android/android_shell.dart index 6c0bb94..37bc0e2 100644 --- a/lib/platform/android/android_shell.dart +++ b/lib/platform/android/android_shell.dart @@ -5,6 +5,13 @@ import 'package:path/path.dart' as p; import '../../services/app_shell.dart'; +/// Android 화면 방향 적용: 'landscape' 가로 고정 / 'portrait' 세로 고정 / 'auto' 기기 방향 따라 (환경 설정 > 화면) +Future applyScreenOrientation(String mode) => SystemChrome.setPreferredOrientations(switch (mode) { + 'portrait' => [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown], + 'auto' => const [], // 빈 목록 = 기기 · 시스템 설정을 따름 + _ => [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight], + }); + /// Android: 창 · 트레이 · 단축키 · 탐색기 연결은 없음. 종료 · 전체 화면 · 웹 주소 열기 · 폴더 열기 (파일 앱). class AndroidShell extends NoopShell { static const _ch = MethodChannel('jj_mkvmaker/android'); diff --git a/lib/ui/browser_page.dart b/lib/ui/browser_page.dart index 2069f4c..cdeadf7 100644 --- a/lib/ui/browser_page.dart +++ b/lib/ui/browser_page.dart @@ -214,6 +214,7 @@ class _BrowserPageState extends State with RouteAware { final bar = m.showSnackBar(SnackBar( content: Text('다운로드 추가: ${_title.isEmpty ? _url : _title}'), duration: const Duration(minutes: 10), + showCloseIcon: true, // [✕] 로 바로 닫기 action: SnackBarAction(label: '목록 보기', onPressed: () => DownloadsPage.open(nav, d)), )); // 받기 준비가 끝나면 (진행률이 나오거나 · 끝 · 실패 · 취소 · 목록에서 지움) 알림을 닫는다 diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index 096cd50..3943b59 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -14,6 +14,7 @@ import 'setup_dialog.dart'; import 'update_dialog.dart'; import '../app/settings.dart'; import '../platform/windows/cef_runtime.dart'; +import '../platform/android/android_shell.dart' show applyScreenOrientation; import 'android_file_browser.dart'; import 'app_actions.dart'; import 'cef_setup.dart'; @@ -289,6 +290,23 @@ class _SettingsPageState extends State { ), ], _section('화면'), + if (!desk) + ListTile( + title: const Text('화면 방향'), + subtitle: const Text('가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)'), + trailing: DropdownButton( + value: s.screenOrientation, + items: const [ + DropdownMenuItem(value: 'landscape', child: Text('가로 고정 (기본)')), + DropdownMenuItem(value: 'portrait', child: Text('세로 고정')), + DropdownMenuItem(value: 'auto', child: Text('자동')), + ], + onChanged: (v) { + c.updateSettings((x) => x.screenOrientation = v!); + applyScreenOrientation(v!); + }, + ), + ), ListTile( title: const Text('기본 화면 크기'), subtitle: Text('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 ' diff --git a/test/browser_test.dart b/test/browser_test.dart index 8c525c7..694ac60 100644 --- a/test/browser_test.dart +++ b/test/browser_test.dart @@ -260,10 +260,21 @@ void main() { expect(find.text('목록 보기'), findsOneWidget); await tester.pump(const Duration(seconds: 30)); expect(find.text('목록 보기'), findsOneWidget, reason: '준비 중에는 그대로'); + expect(find.byIcon(Icons.close), findsOneWidget); // [✕] 로 바로 닫을 수도 있다 d.tasks.first.progress = 0.1; d.refresh(); await settle(); expect(find.text('목록 보기'), findsNothing); + + // [✕] 누르면 바로 닫힘 + host.onUrl('https://www.youtube.com/watch?v=ghi'); + await tester.pump(); + await tester.tap(find.text('다운로드')); + await settle(); + expect(find.text('목록 보기'), findsOneWidget); + await tester.tap(find.byIcon(Icons.close)); + await settle(); + expect(find.text('목록 보기'), findsNothing); d.dispose(); }); diff --git a/test/player_test.dart b/test/player_test.dart index 0e2f16a..16341b1 100644 --- a/test/player_test.dart +++ b/test/player_test.dart @@ -159,6 +159,16 @@ void main() { final back = AppSettings.fromJson(s.toJson()); expect([back.openFileAction, back.openFileWindow], ['add', 'new']); }); + + test('Android 화면 방향: 기본 가로 고정, 세로 · 자동 저장, 모르는 값은 가로', () { + final s = AppSettings(); + expect(s.screenOrientation, 'landscape'); + for (final m in ['portrait', 'auto', 'landscape']) { + s.screenOrientation = m; + expect(AppSettings.fromJson(s.toJson()).screenOrientation, m); + } + expect(AppSettings.fromJson({'screenOrientation': 'sideways'}).screenOrientation, 'landscape'); + }); }); test('중복 실행 방지: 나중 실행은 넘기고, 짧은 시간의 요청은 하나로', () async {