다운로드 알림 [✕] 닫기 · Android 화면 방향 설정 (가로 고정 · 세로 고정 · 자동)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
RyuJongJin 2026-10-04 13:17:25 +09:00
parent 14b2489938
commit bca0c04459
7 changed files with 58 additions and 3 deletions

View File

@ -74,6 +74,9 @@ class AppSettings {
/// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가 /// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가
String openFileAction = 'play'; String openFileAction = 'play';
/// Android 화면 방향: 'landscape' 가로 고정 (기본) / 'portrait' 세로 고정 / 'auto' 기기 방향 따라
String screenOrientation = 'landscape';
/// 프로그램이 켜져 있을 때 탐색기에서 연 동영상 재생: 'same' 켜져 있는 창에서 / 'new' 새 재생 창 /// 프로그램이 켜져 있을 때 탐색기에서 연 동영상 재생: 'same' 켜져 있는 창에서 / 'new' 새 재생 창
String openFileWindow = 'same'; String openFileWindow = 'same';
String homeUrl = 'https://www.youtube.com/'; String homeUrl = 'https://www.youtube.com/';
@ -134,6 +137,7 @@ class AppSettings {
'uiScale': uiScale, 'uiScale': uiScale,
'uiScaleDefault': uiScaleDefault, 'uiScaleDefault': uiScaleDefault,
'openFileAction': openFileAction, 'openFileAction': openFileAction,
'screenOrientation': screenOrientation,
'openFileWindow': openFileWindow, 'openFileWindow': openFileWindow,
'homeUrl': homeUrl, 'homeUrl': homeUrl,
'browserEngine': browserEngine, 'browserEngine': browserEngine,
@ -181,6 +185,9 @@ class AppSettings {
..uiScale = clampUiScale(j['uiScale'] as num?) ..uiScale = clampUiScale(j['uiScale'] as num?)
..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?) ..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?)
..openFileAction = j['openFileAction'] == 'add' ? 'add' : 'play' ..openFileAction = j['openFileAction'] == 'add' ? 'add' : 'play'
..screenOrientation = const ['portrait', 'auto'].contains(j['screenOrientation'])
? j['screenOrientation'] as String
: 'landscape'
..openFileWindow = j['openFileWindow'] == 'new' ? 'new' : 'same' ..openFileWindow = j['openFileWindow'] == 'new' ? 'new' : 'same'
..homeUrl = j['homeUrl'] as String? ?? 'https://www.youtube.com/' ..homeUrl = j['homeUrl'] as String? ?? 'https://www.youtube.com/'
..browserEngine = j['browserEngine'] as String? ?? 'edge' ..browserEngine = j['browserEngine'] as String? ?? 'edge'

View File

@ -16,6 +16,7 @@ import 'core/app_update.dart';
import 'core/playlist.dart'; import 'core/playlist.dart';
import 'platform/android/android_download_tools.dart'; import 'platform/android/android_download_tools.dart';
import 'platform/android/android_keep_alive.dart'; import 'platform/android/android_keep_alive.dart';
import 'platform/android/android_shell.dart' show applyScreenOrientation;
import 'platform/android/android_storage.dart'; import 'platform/android/android_storage.dart';
import 'platform/windows/app_paths.dart'; import 'platform/windows/app_paths.dart';
import 'platform/windows/cef_runtime.dart'; import 'platform/windows/cef_runtime.dart';
@ -354,15 +355,15 @@ String lastWorkText(AppController c) {
} }
/// Android: 창 하나. 트레이 · 단축키 · 탐색기 연결 · 다운로드 (yt-dlp · aria2) · 업데이트는 없다. /// Android: 창 하나. 트레이 · 단축키 · 탐색기 연결 · 다운로드 (yt-dlp · aria2) · 업데이트는 없다.
/// 데스크톱 화면을 그대로 쓰므로 가로 화면으로 띄운다. /// 데스크톱 화면을 그대로 쓰므로 기본은 가로 화면 (환경 설정 > 화면 방향에서 세로 · 자동으로 바꿀 수 있음).
Future<void> runAndroid(String dataDir) async { Future<void> runAndroid(String dataDir) async {
await SystemChrome.setPreferredOrientations( await applyScreenOrientation('landscape');
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
final services = PlatformServices.create(); final services = PlatformServices.create();
final logs = Directory(p.join(dataDir, 'Logs'))..createSync(recursive: true); final logs = Directory(p.join(dataDir, 'Logs'))..createSync(recursive: true);
final controller = AppController(services, settingsStore: SettingsStore())..logFile = p.join(logs.path, 'app.log'); final controller = AppController(services, settingsStore: SettingsStore())..logFile = p.join(logs.path, 'app.log');
controller.note('── 시작 $appTitle (Android) ──'); controller.note('── 시작 $appTitle (Android) ──');
await controller.init(); await controller.init();
await applyScreenOrientation(controller.settings.screenOrientation);
final bookmarks = BookmarksController(); final bookmarks = BookmarksController();
await bookmarks.load(); await bookmarks.load();
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();

View File

@ -5,6 +5,13 @@ import 'package:path/path.dart' as p;
import '../../services/app_shell.dart'; import '../../services/app_shell.dart';
/// Android 화면 방향 적용: 'landscape' 가로 고정 / 'portrait' 세로 고정 / 'auto' 기기 방향 따라 (환경 설정 > 화면)
Future<void> applyScreenOrientation(String mode) => SystemChrome.setPreferredOrientations(switch (mode) {
'portrait' => [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
'auto' => const <DeviceOrientation>[], // 빈 목록 = 기기 · 시스템 설정을 따름
_ => [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight],
});
/// Android: 창 · 트레이 · 단축키 · 탐색기 연결은 없음. 종료 · 전체 화면 · 웹 주소 열기 · 폴더 열기 (파일 앱). /// Android: 창 · 트레이 · 단축키 · 탐색기 연결은 없음. 종료 · 전체 화면 · 웹 주소 열기 · 폴더 열기 (파일 앱).
class AndroidShell extends NoopShell { class AndroidShell extends NoopShell {
static const _ch = MethodChannel('jj_mkvmaker/android'); static const _ch = MethodChannel('jj_mkvmaker/android');

View File

@ -214,6 +214,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
final bar = m.showSnackBar(SnackBar( final bar = m.showSnackBar(SnackBar(
content: Text('다운로드 추가: ${_title.isEmpty ? _url : _title}'), content: Text('다운로드 추가: ${_title.isEmpty ? _url : _title}'),
duration: const Duration(minutes: 10), duration: const Duration(minutes: 10),
showCloseIcon: true, // [✕] 로 바로 닫기
action: SnackBarAction(label: '목록 보기', onPressed: () => DownloadsPage.open(nav, d)), action: SnackBarAction(label: '목록 보기', onPressed: () => DownloadsPage.open(nav, d)),
)); ));
// 받기 준비가 끝나면 (진행률이 나오거나 · 끝 · 실패 · 취소 · 목록에서 지움) 알림을 닫는다 // 받기 준비가 끝나면 (진행률이 나오거나 · 끝 · 실패 · 취소 · 목록에서 지움) 알림을 닫는다

View File

@ -14,6 +14,7 @@ import 'setup_dialog.dart';
import 'update_dialog.dart'; import 'update_dialog.dart';
import '../app/settings.dart'; import '../app/settings.dart';
import '../platform/windows/cef_runtime.dart'; import '../platform/windows/cef_runtime.dart';
import '../platform/android/android_shell.dart' show applyScreenOrientation;
import 'android_file_browser.dart'; import 'android_file_browser.dart';
import 'app_actions.dart'; import 'app_actions.dart';
import 'cef_setup.dart'; import 'cef_setup.dart';
@ -289,6 +290,23 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
], ],
_section('화면'), _section('화면'),
if (!desk)
ListTile(
title: const Text('화면 방향'),
subtitle: const Text('가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)'),
trailing: DropdownButton<String>(
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( ListTile(
title: const Text('기본 화면 크기'), title: const Text('기본 화면 크기'),
subtitle: Text('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 ' subtitle: Text('글자 · 버튼 크기입니다. 위쪽 막대의 − · + 로 그때그때 바꿀 수 있고, 가운데 숫자를 누르면 이 크기로 돌아갑니다 '

View File

@ -260,10 +260,21 @@ void main() {
expect(find.text('목록 보기'), findsOneWidget); expect(find.text('목록 보기'), findsOneWidget);
await tester.pump(const Duration(seconds: 30)); await tester.pump(const Duration(seconds: 30));
expect(find.text('목록 보기'), findsOneWidget, reason: '준비 중에는 그대로'); expect(find.text('목록 보기'), findsOneWidget, reason: '준비 중에는 그대로');
expect(find.byIcon(Icons.close), findsOneWidget); // [✕] 로 바로 닫을 수도 있다
d.tasks.first.progress = 0.1; d.tasks.first.progress = 0.1;
d.refresh(); d.refresh();
await settle(); await settle();
expect(find.text('목록 보기'), findsNothing); 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(); d.dispose();
}); });

View File

@ -159,6 +159,16 @@ void main() {
final back = AppSettings.fromJson(s.toJson()); final back = AppSettings.fromJson(s.toJson());
expect([back.openFileAction, back.openFileWindow], ['add', 'new']); 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 { test('중복 실행 방지: 나중 실행은 넘기고, 짧은 시간의 요청은 하나로', () async {