Android 화면 방향: 기본 자동 (가로 고정 강제 없앰)

근거: 사용자 지적 ("가로 · 세로를 왜 막았나, 지시한 적 없다") + 감독 세션 지시.
예전 결정 (bf6429d · bca0c04, 내 판단 - 사용자 지시 없음): "데스크톱 화면이니까" 시작할 때 가로 고정을 강제하고
기본값 · 모르는 값도 가로 고정으로 해 휴대폰 · 태블릿의 세로 보기를 막았다.

- 시작할 때 가로 고정 강제 (runAndroid) 삭제
- 기본값 auto, 값이 없거나 틀리면 auto
- 예전 설정 파일 (orientationV2 없음) 의 가로 고정은 사용자가 고른 값이 아니므로 한 번 auto 로
- applyScreenOrientation: 모르는 값은 기기 방향 따라 (빈 목록)
- 환경 설정 > 화면 방향: 자동 (기본) · 가로 고정 · 세로 고정
- 시험: player_test 화면 방향

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음3
RyuJongJin 2026-10-08 22:08:15 +09:00
parent 9d7df5f555
commit b39678f8f6
8 changed files with 24 additions and 17 deletions

View File

@ -257,7 +257,7 @@
"⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM was not ready, so it was prepared again (needed for the browser · drag and drop)", "⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM was not ready, so it was prepared again (needed for the browser · drag and drop)",
"⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ The last run did not end normally (started {0}, last check {1}). Earlier records are in app.log in the Logs folder.", "⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ The last run did not end normally (started {0}, last check {1}). Earlier records are in app.log in the Logs folder.",
"가로 16:9": "Landscape 16:9", "가로 16:9": "Landscape 16:9",
"가로 고정 (기본)": "Landscape lock (default)", "가로 고정": "Landscape only",
"가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "Lock landscape · lock portrait, or auto (follows how you hold the device and its auto-rotate setting)", "가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "Lock landscape · lock portrait, or auto (follows how you hold the device and its auto-rotate setting)",
"가입 → 프로필 → API consumers → New consumer": "Sign up → Profile → API consumers → New consumer", "가입 → 프로필 → API consumers → New consumer": "Sign up → Profile → API consumers → New consumer",
"가져오기": "Import", "가져오기": "Import",
@ -945,6 +945,7 @@
"읽을 수 없음": "Cannot read", "읽을 수 없음": "Cannot read",
"임시 파일 · 남은 조각 정리": "Clean temporary files · leftovers", "임시 파일 · 남은 조각 정리": "Clean temporary files · leftovers",
"자동": "Auto", "자동": "Auto",
"자동 (기본)": "Auto (default)",
"자동 감지": "Auto-detect", "자동 감지": "Auto-detect",
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "Automatic installation is not possible here. Please download it from the page yourself.", "자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "Automatic installation is not possible here. Please download it from the page yourself.",
"자막": "Subtitles", "자막": "Subtitles",

View File

@ -257,7 +257,7 @@
"⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM の準備状態が解除されていたため、再準備しました(ブラウザー・ドラッグ&ドロップに必要)", "⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM の準備状態が解除されていたため、再準備しました(ブラウザー・ドラッグ&ドロップに必要)",
"⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ 前回の実行が正常に終了しませんでした(開始 {0}、最終確認 {1})。それ以前の記録は Logs フォルダーの app.log にあります。", "⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ 前回の実行が正常に終了しませんでした(開始 {0}、最終確認 {1})。それ以前の記録は Logs フォルダーの app.log にあります。",
"가로 16:9": "横 16:9", "가로 16:9": "横 16:9",
"가로 고정 (기본)": "横向き固定(既定)", "가로 고정": "横向き固定",
"가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "横向き固定・縦向き固定、または自動(端末の向きに合わせ、端末の自動回転設定に従う)", "가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "横向き固定・縦向き固定、または自動(端末の向きに合わせ、端末の自動回転設定に従う)",
"가입 → 프로필 → API consumers → New consumer": "登録 → プロフィール → API consumers → New consumer", "가입 → 프로필 → API consumers → New consumer": "登録 → プロフィール → API consumers → New consumer",
"가져오기": "取り込む", "가져오기": "取り込む",
@ -945,6 +945,7 @@
"읽을 수 없음": "読み取れません", "읽을 수 없음": "読み取れません",
"임시 파일 · 남은 조각 정리": "一時ファイル・残った断片を整理", "임시 파일 · 남은 조각 정리": "一時ファイル・残った断片を整理",
"자동": "自動", "자동": "自動",
"자동 (기본)": "自動 (既定)",
"자동 감지": "自動検出", "자동 감지": "自動検出",
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "自動インストールできない場所です。ページから直接ダウンロードしてください。", "자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "自動インストールできない場所です。ページから直接ダウンロードしてください。",
"자막": "字幕", "자막": "字幕",

View File

@ -257,7 +257,7 @@
"⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM 未就绪,已重新初始化(浏览器 · 拖放需要)", "⚠ Windows COM 준비 상태가 풀려 있어 다시 준비했습니다 (브라우저 · 끌어다 놓기에 필요)": "⚠ Windows COM 未就绪,已重新初始化(浏览器 · 拖放需要)",
"⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ 上次运行未正常结束(开始 {0},最后检查 {1})。之前的记录在 Logs 文件夹的 app.log 中。", "⚠ 지난 실행이 정상적으로 끝나지 않았습니다 (시작 {0}, 마지막 확인 {1}). 그 전의 기록은 Logs 폴더의 app.log 에 있습니다.": "⚠ 上次运行未正常结束(开始 {0},最后检查 {1})。之前的记录在 Logs 文件夹的 app.log 中。",
"가로 16:9": "横屏 16:9", "가로 16:9": "横屏 16:9",
"가로 고정 (기본)": "锁定横屏(默认)", "가로 고정": "固定横屏",
"가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "锁定横屏 · 锁定竖屏,或自动(随设备方向旋转,遵循设备的自动旋转设置)", "가로 고정 · 세로 고정, 또는 자동 (기기를 돌리는 대로, 기기의 자동 회전 설정을 따름)": "锁定横屏 · 锁定竖屏,或自动(随设备方向旋转,遵循设备的自动旋转设置)",
"가입 → 프로필 → API consumers → New consumer": "注册 → 个人资料 → API consumers → New consumer", "가입 → 프로필 → API consumers → New consumer": "注册 → 个人资料 → API consumers → New consumer",
"가져오기": "导入", "가져오기": "导入",
@ -945,6 +945,7 @@
"읽을 수 없음": "无法读取", "읽을 수 없음": "无法读取",
"임시 파일 · 남은 조각 정리": "清理临时文件 · 残留碎片", "임시 파일 · 남은 조각 정리": "清理临时文件 · 残留碎片",
"자동": "自动", "자동": "自动",
"자동 (기본)": "自动 (默认)",
"자동 감지": "自动检测", "자동 감지": "自动检测",
"자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "此位置无法自动安装。请从页面手动下载。", "자동 설치를 할 수 없는 위치입니다. 페이지에서 직접 받아 주세요.": "此位置无法自动安装。请从页面手动下载。",
"자막": "字幕", "자막": "字幕",

View File

@ -254,8 +254,9 @@ class AppSettings {
/// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가 /// 탐색기에서 동영상을 열었을 때 (더블클릭 · 연결 프로그램): 'play' 바로 재생 / 'add' 편집 목록에 추가
String openFileAction = 'play'; String openFileAction = 'play';
/// Android 화면 방향: 'landscape' 가로 고정 (기본) / 'portrait' 세로 고정 / 'auto' 기기 방향 따라 /// Android 화면 방향: 'auto' 기기 방향 따라 (기본) / 'landscape' 가로 고정 / 'portrait' 세로 고정.
String screenOrientation = 'landscape'; /// 값이 없거나 틀리면 자동 (제한 없는 쪽). 고정은 사용자가 환경 설정에서 고를 때만.
String screenOrientation = 'auto';
/// 화면 언어 (ko · en · ja · zh-Hans, 또는 더한 언어) /// 화면 언어 (ko · en · ja · zh-Hans, 또는 더한 언어)
String uiLanguage = 'ko'; String uiLanguage = 'ko';
@ -448,6 +449,8 @@ class AppSettings {
'uiScaleDefault': uiScaleDefault, 'uiScaleDefault': uiScaleDefault,
'openFileAction': openFileAction, 'openFileAction': openFileAction,
'screenOrientation': screenOrientation, 'screenOrientation': screenOrientation,
// 기본이 가로 고정이던 때의 값은 한 번 자동으로 옮긴다 (사용자가 정한 적 없는 가로 고정)
'orientationV2': true,
'uiLanguage': uiLanguage, 'uiLanguage': uiLanguage,
'appIcon': appIcon, 'appIcon': appIcon,
'uiLanguagesAdded': uiLanguagesAdded, 'uiLanguagesAdded': uiLanguagesAdded,
@ -560,9 +563,9 @@ class AppSettings {
..uiLanguage = j['uiLanguage'] as String? ?? 'ko' ..uiLanguage = j['uiLanguage'] as String? ?? 'ko'
..appIcon = appIconOf(j['appIcon'] as String?) ..appIcon = appIconOf(j['appIcon'] as String?)
..uiLanguagesAdded = [for (final x in (j['uiLanguagesAdded'] as List?) ?? const []) '$x'] ..uiLanguagesAdded = [for (final x in (j['uiLanguagesAdded'] as List?) ?? const []) '$x']
..screenOrientation = const ['portrait', 'auto'].contains(j['screenOrientation']) ..screenOrientation = j['orientationV2'] == true && const ['landscape', 'portrait', 'auto'].contains(j['screenOrientation'])
? j['screenOrientation'] as String ? j['screenOrientation'] as String
: 'landscape' : 'auto'
..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

@ -389,9 +389,8 @@ 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 applyScreenOrientation('landscape');
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');

View File

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

View File

@ -336,9 +336,9 @@ class _SettingsPageState extends State<SettingsPage> {
trailing: DropdownButton<String>( trailing: DropdownButton<String>(
value: s.screenOrientation, value: s.screenOrientation,
items: [ items: [
DropdownMenuItem(value: 'landscape', child: Text(tr('가로 고정 (기본)'))), DropdownMenuItem(value: 'auto', child: Text(tr('자동 (기본)'))),
DropdownMenuItem(value: 'landscape', child: Text(tr('가로 고정'))),
DropdownMenuItem(value: 'portrait', child: Text(tr('세로 고정'))), DropdownMenuItem(value: 'portrait', child: Text(tr('세로 고정'))),
DropdownMenuItem(value: 'auto', child: Text(tr('자동'))),
], ],
onChanged: (v) { onChanged: (v) {
c.updateSettings((x) => x.screenOrientation = v!); c.updateSettings((x) => x.screenOrientation = v!);

View File

@ -160,14 +160,16 @@ void main() {
expect([back.openFileAction, back.openFileWindow], ['add', 'new']); expect([back.openFileAction, back.openFileWindow], ['add', 'new']);
}); });
test('Android 화면 방향: 기본 가로 고정, 세로 · 자동 저장, 모르는 값은 가로', () { test('Android 화면 방향: 기본 자동, 가로 · 세로 고정 저장, 예전 기본 (가로 고정) 은 한 번 자동으로', () {
final s = AppSettings(); final s = AppSettings();
expect(s.screenOrientation, 'landscape'); expect(s.screenOrientation, 'auto');
for (final m in ['portrait', 'auto', 'landscape']) { for (final m in ['portrait', 'auto', 'landscape']) {
s.screenOrientation = m; s.screenOrientation = m;
expect(AppSettings.fromJson(s.toJson()).screenOrientation, m); expect(AppSettings.fromJson(s.toJson()).screenOrientation, m);
} }
expect(AppSettings.fromJson({'screenOrientation': 'sideways'}).screenOrientation, 'landscape'); expect(AppSettings.fromJson({'screenOrientation': 'sideways', 'orientationV2': true}).screenOrientation, 'auto');
// 예전 설정 파일 (기본 가로 고정 시절) → 자동
expect(AppSettings.fromJson({'screenOrientation': 'landscape'}).screenOrientation, 'auto');
}); });
}); });