MKV 화면 · 맨 위 줄 · 브라우저 주소 · 재생 버튼 · 브라우저 소리 설정 · 정리 창 체크
근거:
- 감독 세션 지시 (폰 · S10 Ultra 를 사용자처럼 써 본 결과) 2 · 3 · 4 · 7 · 8 · 9 · 13 · 15번
- 사용자 결정 (10/8): 브라우저 소리 멈춤을 설정으로 (기본 켜짐), 정리 창은 임시 · 받다 만 다운로드만 미리 체크
- 2: 좁은 화면 MKV 화면 둘째 줄은 글자를 숨기지 않고 옆으로 밀어 봄, [MKV 만들기] 맨 앞.
변환 설정 줄은 줄바꿈 ([화면 · 색 보정] 이 세로에서 안 보이던 것).
- 3: 좁은 화면 맨 위 줄: 이동 아이콘 아래 짧은 이름 (홈 · MKV · 브라우저 · 탐색기 · Rsync · 다운로드 · 뒤로),
[환경 설정] 옆에는 ⋮ 메뉴 (화면 크게 · 작게 · 기본 크기 · 종료) - 종료가 설정 바로 옆이라 잘못 누르던 것.
- 4 · 15: 브라우저 주소 칸이 좁으면 (폰 세로 · 태블릿 세로 · 폰 가로) 홈 · 번역 · 즐겨찾기 · 작업 현황 · 외부 브라우저를
⋮ 메뉴로 (없애지 않음). 주소가 "http" 만 보이거나 사라지던 것.
- 7: Android 에서 "탐색기에서 끌어다 놓아도" 안내 뺌. 켤 때 "⚠ 사용할 수 없는 코덱" 기록 뺌 (코덱 목록에 "(사용 불가)" 로 보임).
- 8: 좁은 화면에 동영상이 없으면 한 화면에 안내 + [동영상 추가].
- 9: Android 재생 화면 버튼 크게, 재생 버튼 줄과 나머지 줄을 나눔.
- 13: 넓은 화면의 동영상 목록 칸을 화면 너비의 40% (320~640) 로 - 태블릿 세로에서 제목이 앞부분만 보이던 것.
- 사용자 결정: 환경 설정 > 웹 브라우저 "다른 화면으로 가면 페이지 소리 멈춤" (기본 켜짐).
- 사용자 결정: 저장 공간 정리 창은 작업 임시 파일 · 받다 만 다운로드만 미리 체크.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음3
parent
fead23ce2f
commit
951a1c927c
|
|
@ -276,6 +276,7 @@
|
|||
"검색 지우기": "Clear search",
|
||||
"검정": "Black",
|
||||
"결과 폴더": "Output folder",
|
||||
"결과 폴더 열기": "Open output folder",
|
||||
"결과 폴더 열기 (동영상을 고르세요)": "Open output folder (choose a video)",
|
||||
"결과 폴더 열기: {0}": "Open output folder: {0}",
|
||||
"결과물 이동 실패: {0} ({1})": "Failed to move result: {0} ({1})",
|
||||
|
|
@ -313,6 +314,7 @@
|
|||
"기본": "Default",
|
||||
"기본 앱으로 열기": "Open with default app",
|
||||
"기본 연결 프로그램": "Default app",
|
||||
"기본 크기 ({0}%)": "Default size ({0}%)",
|
||||
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "Back to the default size ({0}%) · set the default size in Settings > Display",
|
||||
"기본 프로그램": "Default program",
|
||||
"기본 프로그램으로 열기": "Open with the default program",
|
||||
|
|
@ -328,6 +330,7 @@
|
|||
"끄면 마지막 설정으로 바로 시작: 원어 {0}, 언어 {1}, {2}": "When off, starts right away with the last settings: source {0}, languages {1}, {2}",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). Chrome 엔진은 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "When off, page scripts do not run (fewer ads · trackers, but script-built sites such as YouTube will not show). With the Chrome engine, page translation · video detection also stop.",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "When off, page scripts do not run (fewer ads · trackers, but script-built sites such as YouTube will not show). Page translation · video detection also stop.",
|
||||
"끄면 환경 설정 · 다운로드 목록 · 다른 화면을 보는 동안에도 웹 페이지의 소리가 계속 들립니다": "When off, the web page keeps playing sound while you look at settings, the download list or other screens",
|
||||
"끌어다 놓은 항목에 동영상이 없습니다.": "There are no videos in the dropped items.",
|
||||
"끝": "End",
|
||||
"끝 (오른쪽 · 아래)": "Edge (right · bottom)",
|
||||
|
|
@ -396,6 +399,7 @@
|
|||
"다른 창으로 복사": "Copy to other pane",
|
||||
"다른 창으로 이동": "Move to other pane",
|
||||
"다른 폴더로 이동": "Move to another folder",
|
||||
"다른 화면으로 가면 페이지 소리 멈춤": "Pause page sound when leaving the browser",
|
||||
"다시 만들기 대기로": "Back to waiting for remake",
|
||||
"다시 설치": "Reinstall",
|
||||
"다시 세기": "Recount",
|
||||
|
|
@ -609,6 +613,7 @@
|
|||
"분석 실패: {0} - {1}": "Analysis failed: {0} - {1}",
|
||||
"붙여넣기": "Paste",
|
||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "Nothing to paste. First hold items with [Copy] · [Move].",
|
||||
"브라우저": "Browser",
|
||||
"브라우저 엔진 (앱 안)": "Browser engine (in-app)",
|
||||
"브라우저 쿠키를 읽지 못했습니다. 환경 설정 > 다운로드 > YouTube 쿠키 에서 \"앱 안 브라우저\" 를 고르거나, 고른 브라우저가 설치되어 있는지 확인하세요 (Chrome · Edge 는 브라우저를 닫아야 읽힙니다).": "Could not read the browser cookies. In Settings > Downloads > YouTube cookies, choose \"In-app browser\", or check that the chosen browser is installed (Chrome · Edge must be closed to be read).",
|
||||
"비공개 · 회원 전용 영상입니다.": "This is a private · members-only video.",
|
||||
|
|
@ -896,6 +901,7 @@
|
|||
"이 파일에 맞는 자막 우선 (영상 해시)": "Prefer subtitles that match this file (video hash)",
|
||||
"이 파일용": "For this file",
|
||||
"이 파일을 열 수 있는 앱이 없습니다.": "No app can open this file.",
|
||||
"이 페이지 번역": "Translate this page",
|
||||
"이 페이지 지우기": "Delete this page",
|
||||
"이 페이지를 {0}(으)로 번역": "Translate this page to {0}",
|
||||
"이 폴더 선택": "Choose this folder",
|
||||
|
|
@ -1085,6 +1091,7 @@
|
|||
"즐겨찾기": "Bookmarks",
|
||||
"즐겨찾기 HTML 가져오기": "Import bookmarks HTML",
|
||||
"즐겨찾기 검색 (이름 · 주소)": "Search bookmarks (name · address)",
|
||||
"즐겨찾기 관리": "Manage bookmarks",
|
||||
"즐겨찾기 관리 (Ctrl+Shift+B)": "Manage bookmarks (Ctrl+Shift+B)",
|
||||
"즐겨찾기 관리자": "Bookmark manager",
|
||||
"즐겨찾기 관리자 (Ctrl+Shift+O)": "Bookmark manager (Ctrl+Shift+O)",
|
||||
|
|
@ -1098,6 +1105,7 @@
|
|||
"즐겨찾기 추가됨": "Bookmark added",
|
||||
"즐겨찾기 표시줄": "Bookmarks bar",
|
||||
"지금": "Current",
|
||||
"지금 {0}%": "Now {0}%",
|
||||
"지금 도는 복사는 앞의 옵션으로 돌고 있습니다. 멈추고 새 옵션으로 다시 실행할까요?": "The running copy uses the previous options. Stop it and run again with the new options?",
|
||||
"지금 맞추기": "Sync now",
|
||||
"지금 맞추는 중입니다. 끝난 뒤에 하세요.": "Syncing right now. Try again when it finishes.",
|
||||
|
|
@ -1176,6 +1184,7 @@
|
|||
"크메르어": "Khmer",
|
||||
"타갈로그어": "Tagalog",
|
||||
"타밀어": "Tamil",
|
||||
"탐색기": "Explorer",
|
||||
"탐색기 오른쪽 클릭 메뉴": "Explorer right-click menu",
|
||||
"탐색기에서 동영상을 열 때": "When opening a video from Explorer",
|
||||
"탐색기에서 연 동영상을 재생할 창": "Window to play videos opened from Explorer",
|
||||
|
|
@ -1302,6 +1311,7 @@
|
|||
"화면 작게": "Smaller",
|
||||
"화면 크게": "Larger",
|
||||
"화면 크기": "Size",
|
||||
"화면 크기 · 종료": "Screen size · Exit",
|
||||
"화면 크기 따라 (기본)": "By screen size (default)",
|
||||
"화면이 있는 컴포넌트는 하나 이상 남아 있어야 합니다.": "At least one component with a screen must stay installed.",
|
||||
"화질": "Quality",
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@
|
|||
"검색 지우기": "検索をクリア",
|
||||
"검정": "黒",
|
||||
"결과 폴더": "出力フォルダー",
|
||||
"결과 폴더 열기": "出力フォルダーを開く",
|
||||
"결과 폴더 열기 (동영상을 고르세요)": "出力フォルダーを開く(動画を選んでください)",
|
||||
"결과 폴더 열기: {0}": "出力フォルダーを開く: {0}",
|
||||
"결과물 이동 실패: {0} ({1})": "成果物の移動失敗: {0}({1})",
|
||||
|
|
@ -313,6 +314,7 @@
|
|||
"기본": "既定",
|
||||
"기본 앱으로 열기": "既定のアプリで開く",
|
||||
"기본 연결 프로그램": "既定のアプリ",
|
||||
"기본 크기 ({0}%)": "既定のサイズ ({0}%)",
|
||||
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "既定の大きさ({0}%)に戻す・既定の大きさは 環境設定 > 画面 で",
|
||||
"기본 프로그램": "既定のプログラム",
|
||||
"기본 프로그램으로 열기": "既定のプログラムで開く",
|
||||
|
|
@ -328,6 +330,7 @@
|
|||
"끄면 마지막 설정으로 바로 시작: 원어 {0}, 언어 {1}, {2}": "オフにすると前回の設定ですぐ開始: 原語 {0}、言語 {1}、{2}",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). Chrome 엔진은 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "オフにするとページのスクリプトを実行しません (広告 · 追跡は減りますが、YouTube のようにスクリプトで作られたサイトは表示されません)。Chrome エンジンではページ翻訳 · 動画の検出も止まります。",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "オフにするとページのスクリプトを実行しません (広告 · 追跡は減りますが、YouTube のようにスクリプトで作られたサイトは表示されません)。ページ翻訳 · 動画の検出も止まります。",
|
||||
"끄면 환경 설정 · 다운로드 목록 · 다른 화면을 보는 동안에도 웹 페이지의 소리가 계속 들립니다": "オフにすると、環境設定・ダウンロード一覧・他の画面を見ている間も Web ページの音が流れ続けます",
|
||||
"끌어다 놓은 항목에 동영상이 없습니다.": "ドロップした項目に動画がありません。",
|
||||
"끝": "終了",
|
||||
"끝 (오른쪽 · 아래)": "端 (右 · 下)",
|
||||
|
|
@ -396,6 +399,7 @@
|
|||
"다른 창으로 복사": "もう一方のペインへコピー",
|
||||
"다른 창으로 이동": "もう一方のペインへ移動",
|
||||
"다른 폴더로 이동": "他のフォルダーに移動",
|
||||
"다른 화면으로 가면 페이지 소리 멈춤": "他の画面に移るとページの音を止める",
|
||||
"다시 만들기 대기로": "作り直し待ちに戻す",
|
||||
"다시 설치": "再インストール",
|
||||
"다시 세기": "再カウント",
|
||||
|
|
@ -609,6 +613,7 @@
|
|||
"분석 실패: {0} - {1}": "分析失敗: {0} - {1}",
|
||||
"붙여넣기": "貼り付け",
|
||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "貼り付ける項目がありません。先に [コピー] · [移動] で保持してください。",
|
||||
"브라우저": "ブラウザー",
|
||||
"브라우저 엔진 (앱 안)": "ブラウザーエンジン(アプリ内)",
|
||||
"브라우저 쿠키를 읽지 못했습니다. 환경 설정 > 다운로드 > YouTube 쿠키 에서 \"앱 안 브라우저\" 를 고르거나, 고른 브라우저가 설치되어 있는지 확인하세요 (Chrome · Edge 는 브라우저를 닫아야 읽힙니다).": "ブラウザーのクッキーを読めませんでした。環境設定 > ダウンロード > YouTube クッキー で「アプリ内ブラウザー」を選ぶか、選んだブラウザーがインストールされているか確認してください(Chrome・Edge はブラウザーを閉じないと読めません)。",
|
||||
"비공개 · 회원 전용 영상입니다.": "非公開・メンバー限定の動画です。",
|
||||
|
|
@ -896,6 +901,7 @@
|
|||
"이 파일에 맞는 자막 우선 (영상 해시)": "このファイルに合う字幕を優先(動画ハッシュ)",
|
||||
"이 파일용": "このファイル用",
|
||||
"이 파일을 열 수 있는 앱이 없습니다.": "このファイルを開けるアプリがありません。",
|
||||
"이 페이지 번역": "このページを翻訳",
|
||||
"이 페이지 지우기": "このページを削除",
|
||||
"이 페이지를 {0}(으)로 번역": "このページを{0}に翻訳",
|
||||
"이 폴더 선택": "このフォルダーを選択",
|
||||
|
|
@ -1085,6 +1091,7 @@
|
|||
"즐겨찾기": "ブックマーク",
|
||||
"즐겨찾기 HTML 가져오기": "ブックマーク HTML を取り込む",
|
||||
"즐겨찾기 검색 (이름 · 주소)": "ブックマークを検索(名前・アドレス)",
|
||||
"즐겨찾기 관리": "ブックマーク管理",
|
||||
"즐겨찾기 관리 (Ctrl+Shift+B)": "ブックマーク管理(Ctrl+Shift+B)",
|
||||
"즐겨찾기 관리자": "ブックマークマネージャー",
|
||||
"즐겨찾기 관리자 (Ctrl+Shift+O)": "ブックマークマネージャー(Ctrl+Shift+O)",
|
||||
|
|
@ -1098,6 +1105,7 @@
|
|||
"즐겨찾기 추가됨": "ブックマークを追加しました",
|
||||
"즐겨찾기 표시줄": "ブックマークバー",
|
||||
"지금": "現在",
|
||||
"지금 {0}%": "現在 {0}%",
|
||||
"지금 도는 복사는 앞의 옵션으로 돌고 있습니다. 멈추고 새 옵션으로 다시 실행할까요?": "実行中のコピーは前のオプションで動いています。止めて新しいオプションで再実行しますか?",
|
||||
"지금 맞추기": "今すぐ同期",
|
||||
"지금 맞추는 중입니다. 끝난 뒤에 하세요.": "同期中です。終わってから行ってください。",
|
||||
|
|
@ -1176,6 +1184,7 @@
|
|||
"크메르어": "クメール語",
|
||||
"타갈로그어": "タガログ語",
|
||||
"타밀어": "タミル語",
|
||||
"탐색기": "エクスプローラー",
|
||||
"탐색기 오른쪽 클릭 메뉴": "エクスプローラーの右クリックメニュー",
|
||||
"탐색기에서 동영상을 열 때": "エクスプローラーで動画を開いたとき",
|
||||
"탐색기에서 연 동영상을 재생할 창": "エクスプローラーで開いた動画を再生するウィンドウ",
|
||||
|
|
@ -1302,6 +1311,7 @@
|
|||
"화면 작게": "画面を小さく",
|
||||
"화면 크게": "画面を大きく",
|
||||
"화면 크기": "画面サイズ",
|
||||
"화면 크기 · 종료": "画面サイズ・終了",
|
||||
"화면 크기 따라 (기본)": "画面サイズに合わせる (既定)",
|
||||
"화면이 있는 컴포넌트는 하나 이상 남아 있어야 합니다.": "画面のあるコンポーネントを少なくとも1つ残す必要があります。",
|
||||
"화질": "画質",
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@
|
|||
"검색 지우기": "清除搜索",
|
||||
"검정": "黑色",
|
||||
"결과 폴더": "输出文件夹",
|
||||
"결과 폴더 열기": "打开输出文件夹",
|
||||
"결과 폴더 열기 (동영상을 고르세요)": "打开输出文件夹(请选择视频)",
|
||||
"결과 폴더 열기: {0}": "打开输出文件夹: {0}",
|
||||
"결과물 이동 실패: {0} ({1})": "成品移动失败: {0}({1})",
|
||||
|
|
@ -313,6 +314,7 @@
|
|||
"기본": "默认",
|
||||
"기본 앱으로 열기": "用默认应用打开",
|
||||
"기본 연결 프로그램": "默认打开方式",
|
||||
"기본 크기 ({0}%)": "默认大小 ({0}%)",
|
||||
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "恢复默认大小({0}%)· 默认大小在 设置 > 显示 中设置",
|
||||
"기본 프로그램": "默认程序",
|
||||
"기본 프로그램으로 열기": "用默认程序打开",
|
||||
|
|
@ -328,6 +330,7 @@
|
|||
"끄면 마지막 설정으로 바로 시작: 원어 {0}, 언어 {1}, {2}": "关闭后将直接使用上次的设置开始: 原语言 {0},语言 {1},{2}",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). Chrome 엔진은 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "关闭后不运行页面脚本 (广告 · 跟踪会减少,但 YouTube 等由脚本构建的网站将无法显示)。使用 Chrome 引擎时,页面翻译 · 视频检测也会停止。",
|
||||
"끄면 페이지의 스크립트를 실행하지 않습니다 (광고 · 추적이 줄지만 YouTube 처럼 스크립트로 만든 사이트는 보이지 않음). 끄면 페이지 번역 · 동영상 찾기도 멈춥니다.": "关闭后不运行页面脚本 (广告 · 跟踪会减少,但 YouTube 等由脚本构建的网站将无法显示)。页面翻译 · 视频检测也会停止。",
|
||||
"끄면 환경 설정 · 다운로드 목록 · 다른 화면을 보는 동안에도 웹 페이지의 소리가 계속 들립니다": "关闭后,在查看设置、下载列表或其他界面时网页声音会继续播放",
|
||||
"끌어다 놓은 항목에 동영상이 없습니다.": "拖入的项目中没有视频。",
|
||||
"끝": "结束",
|
||||
"끝 (오른쪽 · 아래)": "边缘 (右 · 下)",
|
||||
|
|
@ -396,6 +399,7 @@
|
|||
"다른 창으로 복사": "复制到另一窗格",
|
||||
"다른 창으로 이동": "移动到另一窗格",
|
||||
"다른 폴더로 이동": "移动到其他文件夹",
|
||||
"다른 화면으로 가면 페이지 소리 멈춤": "离开浏览器时暂停网页声音",
|
||||
"다시 만들기 대기로": "改回等待重新制作",
|
||||
"다시 설치": "重新安装",
|
||||
"다시 세기": "重新统计",
|
||||
|
|
@ -609,6 +613,7 @@
|
|||
"분석 실패: {0} - {1}": "分析失败: {0} - {1}",
|
||||
"붙여넣기": "粘贴",
|
||||
"붙여넣을 항목이 없습니다. 먼저 [복사] · [이동] 으로 담으세요.": "没有可粘贴的项目。请先用 [复制] · [移动] 暂存。",
|
||||
"브라우저": "浏览器",
|
||||
"브라우저 엔진 (앱 안)": "浏览器引擎(应用内)",
|
||||
"브라우저 쿠키를 읽지 못했습니다. 환경 설정 > 다운로드 > YouTube 쿠키 에서 \"앱 안 브라우저\" 를 고르거나, 고른 브라우저가 설치되어 있는지 확인하세요 (Chrome · Edge 는 브라우저를 닫아야 읽힙니다).": "未能读取浏览器 Cookie。请在 设置 > 下载 > YouTube Cookie 中选择“应用内浏览器”,或确认所选浏览器已安装(Chrome · Edge 需关闭浏览器才能读取)。",
|
||||
"비공개 · 회원 전용 영상입니다.": "这是私享 · 会员专享视频。",
|
||||
|
|
@ -896,6 +901,7 @@
|
|||
"이 파일에 맞는 자막 우선 (영상 해시)": "优先匹配此文件的字幕(视频哈希)",
|
||||
"이 파일용": "适用于此文件",
|
||||
"이 파일을 열 수 있는 앱이 없습니다.": "没有可以打开此文件的应用。",
|
||||
"이 페이지 번역": "翻译此页",
|
||||
"이 페이지 지우기": "删除此页",
|
||||
"이 페이지를 {0}(으)로 번역": "将此页面翻译为{0}",
|
||||
"이 폴더 선택": "选择此文件夹",
|
||||
|
|
@ -1085,6 +1091,7 @@
|
|||
"즐겨찾기": "书签",
|
||||
"즐겨찾기 HTML 가져오기": "导入书签 HTML",
|
||||
"즐겨찾기 검색 (이름 · 주소)": "搜索书签(名称 · 地址)",
|
||||
"즐겨찾기 관리": "管理书签",
|
||||
"즐겨찾기 관리 (Ctrl+Shift+B)": "管理书签(Ctrl+Shift+B)",
|
||||
"즐겨찾기 관리자": "书签管理器",
|
||||
"즐겨찾기 관리자 (Ctrl+Shift+O)": "书签管理器(Ctrl+Shift+O)",
|
||||
|
|
@ -1098,6 +1105,7 @@
|
|||
"즐겨찾기 추가됨": "已添加书签",
|
||||
"즐겨찾기 표시줄": "书签栏",
|
||||
"지금": "当前",
|
||||
"지금 {0}%": "当前 {0}%",
|
||||
"지금 도는 복사는 앞의 옵션으로 돌고 있습니다. 멈추고 새 옵션으로 다시 실행할까요?": "正在运行的复制使用的是之前的选项。停止并用新选项重新运行吗?",
|
||||
"지금 맞추기": "立即同步",
|
||||
"지금 맞추는 중입니다. 끝난 뒤에 하세요.": "正在同步。请等结束后再进行。",
|
||||
|
|
@ -1176,6 +1184,7 @@
|
|||
"크메르어": "高棉语",
|
||||
"타갈로그어": "他加禄语",
|
||||
"타밀어": "泰米尔语",
|
||||
"탐색기": "文件管理",
|
||||
"탐색기 오른쪽 클릭 메뉴": "资源管理器右键菜单",
|
||||
"탐색기에서 동영상을 열 때": "从资源管理器打开视频时",
|
||||
"탐색기에서 연 동영상을 재생할 창": "播放从资源管理器打开的视频的窗口",
|
||||
|
|
@ -1302,6 +1311,7 @@
|
|||
"화면 작게": "缩小界面",
|
||||
"화면 크게": "放大界面",
|
||||
"화면 크기": "画面大小",
|
||||
"화면 크기 · 종료": "界面大小 · 退出",
|
||||
"화면 크기 따라 (기본)": "按屏幕大小 (默认)",
|
||||
"화면이 있는 컴포넌트는 하나 이상 남아 있어야 합니다.": "至少要保留一个带界面的组件。",
|
||||
"화질": "画质",
|
||||
|
|
|
|||
|
|
@ -177,11 +177,8 @@ class AppController extends ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
encoders = await _tool.encoders();
|
||||
final missing = [
|
||||
for (final c in VideoCodecChoice.values)
|
||||
if (c != VideoCodecChoice.copy && !isCodecAvailable(c)) c.label,
|
||||
];
|
||||
_log(trf('FFmpeg 준비됨: {0}' '{1}', [ffmpegVersion, missing.isEmpty ? '' : trf(' ⚠ 사용할 수 없는 코덱: {0}', [missing.join(', ')])]));
|
||||
// 이 기기에 없는 코덱은 코덱 고르기 목록에 "(사용 불가)" 로 보인다. 켤 때마다 경고처럼 남기지 않는다 (고장으로 보였음)
|
||||
_log(trf('FFmpeg 준비됨: {0}' '{1}', [ffmpegVersion, '']));
|
||||
}
|
||||
|
||||
bool isCodecAvailable(VideoCodecChoice c) =>
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ class AppSettings {
|
|||
/// YouTube 페이지의 광고 배너 · 광고 영역 숨기기
|
||||
bool youtubeAdHide = true;
|
||||
|
||||
/// 웹 브라우저 위에 다른 화면이 올라오거나 다른 화면으로 가면 페이지의 동영상 · 소리를 멈춤 (끄면 계속 들림)
|
||||
bool webPauseOnLeave = true;
|
||||
|
||||
/// 다운로드 위치 (아래에 jj_yt-dlp, jj_aria2 생성). null 이면 프로그램 폴더
|
||||
String? downloadRoot;
|
||||
|
||||
|
|
@ -426,6 +429,7 @@ class AppSettings {
|
|||
'moveTargets': [for (final t in moveTargets) t.toJson()],
|
||||
'youtubeAdSkip': youtubeAdSkip,
|
||||
'youtubeAdHide': youtubeAdHide,
|
||||
'webPauseOnLeave': webPauseOnLeave,
|
||||
'downloadRoot': downloadRoot,
|
||||
'clipboardWatch': clipboardWatch,
|
||||
'minimizeToTray': minimizeToTray,
|
||||
|
|
@ -542,6 +546,7 @@ class AppSettings {
|
|||
]
|
||||
..youtubeAdSkip = j['youtubeAdSkip'] as bool? ?? true
|
||||
..youtubeAdHide = j['youtubeAdHide'] as bool? ?? true
|
||||
..webPauseOnLeave = j['webPauseOnLeave'] as bool? ?? true
|
||||
..downloadRoot = j['downloadRoot'] as String?
|
||||
..clipboardWatch = j['clipboardWatch'] as bool? ?? true
|
||||
..minimizeToTray = j['minimizeToTray'] as bool? ?? true
|
||||
|
|
|
|||
|
|
@ -203,7 +203,33 @@ class AppNavButtons extends StatelessWidget {
|
|||
'downloads' => here ? tr('다운로드 목록 (지금 여기)') : tr('다운로드 목록'),
|
||||
_ => id,
|
||||
};
|
||||
Widget btn(Widget icon, String tip, VoidCallback? f, {bool here = false}) => SizedBox(
|
||||
// 좁은 화면 (폰): 아이콘 아래에 짧은 이름 - 글자 없는 작은 아이콘만 늘어서 무엇인지 몰랐던 것
|
||||
final labels = isCompact(context);
|
||||
Widget btn(Widget icon, String tip, VoidCallback? f, {bool here = false, String label = ''}) => labels
|
||||
? Tooltip(
|
||||
message: tip,
|
||||
child: InkWell(
|
||||
onTap: f,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
width: 54,
|
||||
height: 46,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
size: 20, color: here ? JjColors.accent : (f == null ? JjColors.textDim : JjColors.text)),
|
||||
child: icon,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 10, color: here ? JjColors.accent : JjColors.textDim)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: IconButton(
|
||||
|
|
@ -214,12 +240,21 @@ class AppNavButtons extends StatelessWidget {
|
|||
onPressed: f,
|
||||
),
|
||||
);
|
||||
String short(String id) => switch (id) {
|
||||
'mkv' => 'MKV',
|
||||
'browser' => tr('브라우저'),
|
||||
'explorer' => tr('탐색기'),
|
||||
'rsync' => 'Rsync',
|
||||
'downloads' => tr('다운로드'),
|
||||
_ => id,
|
||||
};
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
btn(
|
||||
Image.asset(appIconButtonAsset(scope?.controller.settings.appIcon),
|
||||
width: 26, height: 26, filterQuality: FilterQuality.medium),
|
||||
width: labels ? 22 : 26, height: labels ? 22 : 26, filterQuality: FilterQuality.medium),
|
||||
trf('홈 화면 ({0}) · 환경 설정에서 바꿈', [homeName]),
|
||||
scope == null ? null : () => toHome(context),
|
||||
label: tr('홈'),
|
||||
),
|
||||
// 설치한 화면들 (환경 설정 > 컴포넌트 의 순서). 하나뿐이면 버튼 줄은 그것만.
|
||||
for (final p in pages)
|
||||
|
|
@ -228,10 +263,12 @@ class AppNavButtons extends StatelessWidget {
|
|||
tip(p.id, p.id == current),
|
||||
p.id == current ? null : () => open(context, p.id),
|
||||
here: p.id == current,
|
||||
label: short(p.id),
|
||||
),
|
||||
btn(const Icon(Icons.arrow_back), tr('뒤로'),
|
||||
// 브라우저 화면: 뒤로 키는 웹 페이지 뒤로 (PopScope) 이지만 이 버튼은 화면 이동이라 바로 닫는다
|
||||
atRoot ? null : () => onBrowserPage ? Navigator.pop(context) : Navigator.maybePop(context)),
|
||||
atRoot ? null : () => onBrowserPage ? Navigator.pop(context) : Navigator.maybePop(context),
|
||||
label: tr('뒤로')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -440,6 +477,62 @@ class AppActions extends StatelessWidget {
|
|||
if (controller == null && exit == null) return const SizedBox();
|
||||
final usage = controller?.usage;
|
||||
final compact = isCompact(context);
|
||||
if (compact) {
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
if (controller != null)
|
||||
IconButton(
|
||||
tooltip: tr('환경 설정'),
|
||||
icon: Icon(onSettingsPage ? Icons.settings : Icons.settings_outlined,
|
||||
color: onSettingsPage ? JjColors.accent : null),
|
||||
onPressed: onSettingsPage
|
||||
? null
|
||||
: () => Navigator.push(context, MaterialPageRoute<void>(builder: (_) => SettingsPage(c: controller))),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
tooltip: tr('화면 크기 · 종료'),
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: (v) {
|
||||
final s = controller?.settings;
|
||||
switch (v) {
|
||||
case 'bigger' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = AppSettings.clampUiScale(s.uiScale + 0.1));
|
||||
case 'smaller' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = AppSettings.clampUiScale(s.uiScale - 0.1));
|
||||
case 'default' when s != null:
|
||||
controller!.updateSettings((x) => x.uiScale = s.uiScaleDefault);
|
||||
case 'exit':
|
||||
exit?.call();
|
||||
}
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
if (controller != null) ...[
|
||||
PopupMenuItem(
|
||||
value: 'bigger',
|
||||
child: ListTile(leading: const Icon(Icons.zoom_in), title: Text(tr('화면 크게')), dense: true)),
|
||||
PopupMenuItem(
|
||||
value: 'smaller',
|
||||
child: ListTile(leading: const Icon(Icons.zoom_out), title: Text(tr('화면 작게')), dense: true)),
|
||||
PopupMenuItem(
|
||||
value: 'default',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.fit_screen_outlined),
|
||||
title: Text(trf('기본 크기 ({0}%)', [(controller.settings.uiScaleDefault * 100).round()])),
|
||||
subtitle: Text(trf('지금 {0}%', [(controller.settings.uiScale * 100).round()])),
|
||||
dense: true)),
|
||||
],
|
||||
if (exit != null) ...[
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(
|
||||
value: 'exit',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.power_settings_new, color: JjColors.danger),
|
||||
title: Text(tr('종료')),
|
||||
dense: true)),
|
||||
],
|
||||
],
|
||||
),
|
||||
]);
|
||||
}
|
||||
return Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
// CPU · MEM: 변환 · AI 작업이 PC 를 얼마나 쓰는지 (PC 전체 기준)
|
||||
if (usage != null && showUsage && !compact) UsageView(usage: usage),
|
||||
|
|
|
|||
|
|
@ -291,7 +291,8 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
@override
|
||||
void didPushNext() {
|
||||
_visible = false;
|
||||
_nav?.pauseMedia();
|
||||
// 환경 설정 > 웹 브라우저 > "다른 화면으로 가면 페이지 소리 멈춤" (끄면 다른 화면에서도 계속 들림)
|
||||
if (widget.c.settings.webPauseOnLeave) _nav?.pauseMedia();
|
||||
}
|
||||
|
||||
/// 설정 화면 등에서 돌아왔다: 바뀐 광고 설정을 지금 페이지에 적용
|
||||
|
|
@ -312,7 +313,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
/// MKV 화면 등으로 가며 브라우저 화면이 닫힌다: 살려 둔 웹뷰에서 소리만 나지 않게 멈춘다
|
||||
@override
|
||||
void didPop() {
|
||||
_nav?.pauseMedia();
|
||||
if (widget.c.settings.webPauseOnLeave) _nav?.pauseMedia();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -519,12 +520,14 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
icon: Icon(i, size: 20, color: color),
|
||||
onPressed: f,
|
||||
);
|
||||
// 좁은 화면: 둘째 줄에 페이지 이동 · 주소 · 다운로드 (외부 브라우저 · 작업 현황은 빼고, 다운로드는 아이콘만)
|
||||
final compact = isCompact(context);
|
||||
// 주소 칸이 좁아지면 (폰 세로 · 태블릿 세로 · 폰 가로 100%) 자주 안 쓰는 버튼은 ⋮ 메뉴로 - 주소가 "http" 만 보이거나
|
||||
// 아예 사라지던 것. 버튼을 없애지는 않는다.
|
||||
return AppTopBar(
|
||||
nav: const AppNavButtons(onBrowserPage: true),
|
||||
actions: AppActions(c: widget.c),
|
||||
middle: Row(children: [
|
||||
middle: LayoutBuilder(builder: (context, box) {
|
||||
final compact = box.maxWidth < 640;
|
||||
return Row(children: [
|
||||
if (!compact) ...[
|
||||
Container(width: 1, height: 24, color: JjColors.border),
|
||||
const SizedBox(width: 4),
|
||||
|
|
@ -535,7 +538,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
_progress < 1
|
||||
? btn(Icons.close, tr('중지'), () => _nav?.stop())
|
||||
: btn(Icons.refresh, tr('새로고침 (F5)'), () => _nav?.reload()),
|
||||
btn(Icons.home_outlined, tr('홈'), () => _go(widget.c.settings.homeUrl)),
|
||||
if (!compact) btn(Icons.home_outlined, tr('홈'), () => _go(widget.c.settings.homeUrl)),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
|
|
@ -561,6 +564,7 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (!compact)
|
||||
btn(
|
||||
Icons.translate,
|
||||
_trOn
|
||||
|
|
@ -596,9 +600,41 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
if (!compact)
|
||||
btn(_work ? Icons.view_sidebar : Icons.view_sidebar_outlined, tr('작업 현황 보기 · 화면 분할 (Ctrl+Shift+J)'),
|
||||
_toggleWork, color: widget.c.busy ? JjColors.accent : null),
|
||||
if (!compact)
|
||||
btn(_panel ? Icons.bookmarks : Icons.bookmarks_outlined, tr('즐겨찾기 관리 (Ctrl+Shift+B)'),
|
||||
() => setState(() => _panel = !_panel)),
|
||||
]),
|
||||
() => setState(() => _panel = !_panel))
|
||||
else
|
||||
PopupMenuButton<String>(
|
||||
tooltip: tr('더 보기'),
|
||||
icon: const Icon(Icons.more_vert, size: 20),
|
||||
onSelected: (v) => switch (v) {
|
||||
'home' => _go(widget.c.settings.homeUrl),
|
||||
'translate' => _setTranslate(!_trOn, force: true),
|
||||
'external' => _openExternal(_url),
|
||||
'work' => _toggleWork(),
|
||||
_ => setState(() => _panel = !_panel),
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
PopupMenuItem(value: 'home', child: ListTile(dense: true, leading: const Icon(Icons.home_outlined), title: Text(tr('홈')))),
|
||||
PopupMenuItem(
|
||||
value: 'translate',
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
leading: Icon(Icons.translate, color: _trOn ? JjColors.accent : null),
|
||||
title: Text(_trOn ? tr('원문 보기 (번역 끄기)') : tr('이 페이지 번역')))),
|
||||
PopupMenuItem(
|
||||
value: 'bookmarks',
|
||||
child: ListTile(dense: true, leading: const Icon(Icons.bookmarks_outlined), title: Text(tr('즐겨찾기 관리')))),
|
||||
PopupMenuItem(
|
||||
value: 'work',
|
||||
child: ListTile(dense: true, leading: const Icon(Icons.view_sidebar_outlined), title: Text(tr('작업 현황')))),
|
||||
PopupMenuItem(
|
||||
value: 'external',
|
||||
child: ListTile(dense: true, leading: const Icon(Icons.open_in_new), title: Text(tr('외부 브라우저로 열기')))),
|
||||
],
|
||||
),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ class _CleanupDialogState extends State<_CleanupDialog> {
|
|||
if (!mounted) return;
|
||||
setState(() {
|
||||
_groups = g;
|
||||
// 처음에는 지울 수 있고 무언가 있는 묶음을 모두 고른 상태로
|
||||
_picked.addAll([for (final x in g) if (x.skipped == null && x.items.isNotEmpty) x.id]);
|
||||
// 처음에는 작업 임시 파일 · 받다 만 다운로드만 고른 상태로 (사용자 결정 10/8).
|
||||
// 받다 만 AI 모델 · 지난 작업 기록 · 업데이트 남은 파일은 직접 체크해야 지운다
|
||||
const pre = {'work', 'download'};
|
||||
_picked.addAll([for (final x in g) if (x.skipped == null && x.items.isNotEmpty && pre.contains(x.id)) x.id]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,10 @@ class HomePage extends StatelessWidget {
|
|||
const Divider(height: 1),
|
||||
Expanded(
|
||||
// 좁은 화면 (접은 폴드 · 휴대폰 세로): 목록을 위에, 자세히 보기를 아래에
|
||||
child: isCompact(context)
|
||||
child: isCompact(context) && c.videos.isEmpty
|
||||
// 8: 좁은 화면에 동영상이 없으면 빈 목록 · 안내로 나누지 않고 한 화면에 안내 + [동영상 추가]
|
||||
? _EmptyHint(onAdd: c.pickVideos)
|
||||
: isCompact(context)
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
|
|
@ -67,7 +70,7 @@ class HomePage extends StatelessWidget {
|
|||
const Divider(height: 1),
|
||||
Expanded(
|
||||
child: c.selected == null
|
||||
? const _EmptyHint()
|
||||
? _EmptyHint(onAdd: c.pickVideos)
|
||||
: _VideoDetail(c: c, v: c.selected!),
|
||||
),
|
||||
],
|
||||
|
|
@ -75,11 +78,14 @@ class HomePage extends StatelessWidget {
|
|||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(width: 320, child: _VideoList(c: c)),
|
||||
// 13: 큰 화면 (태블릿 세로 등) 은 목록도 넓게 - 제목이 앞부분만 보여 구분이 안 되던 것
|
||||
SizedBox(
|
||||
width: (MediaQuery.sizeOf(context).width * 0.4).clamp(320.0, 640.0),
|
||||
child: _VideoList(c: c)),
|
||||
const VerticalDivider(width: 1),
|
||||
Expanded(
|
||||
child: c.selected == null
|
||||
? const _EmptyHint()
|
||||
? _EmptyHint(onAdd: c.pickVideos)
|
||||
: _VideoDetail(c: c, v: c.selected!),
|
||||
),
|
||||
],
|
||||
|
|
@ -193,6 +199,9 @@ class _TopBar extends StatelessWidget {
|
|||
if (need() > w) dlBox = false;
|
||||
if (need() > w) mkvText = false;
|
||||
|
||||
// 7: 폰에는 "탐색기에서 끌어다 놓기" 가 없다
|
||||
final addTip = Platform.isAndroid ? tr('동영상 추가') : tr('동영상 추가 (탐색기에서 끌어다 놓아도 됩니다)');
|
||||
|
||||
// 글이 있는 버튼, 또는 (좁을 때) 아이콘만 있는 버튼
|
||||
Widget action(IconData icon, String label, VoidCallback? onPressed,
|
||||
{required bool text, required String tip, Color? color}) {
|
||||
|
|
@ -210,6 +219,60 @@ class _TopBar extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
// 2: 좁은 화면 (폰 세로) 에서는 버튼 글자를 숨기지 않고 옆으로 밀어 본다. 가장 많이 쓰는 [MKV 만들기] 를 맨 앞에.
|
||||
if (compact) {
|
||||
VoidCallback? build0() =>
|
||||
!canBuild ? null : (batch.isEmpty ? () => _confirmBuildAll(context) : () => c.buildVideos(batch));
|
||||
return AppTopBar(
|
||||
nav: const AppNavButtons(),
|
||||
actions: AppActions(c: c, onExit: onExit, showUsage: false),
|
||||
middle: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: [
|
||||
Tooltip(
|
||||
message: trf('MKV 만들기{0}', [count]),
|
||||
child: FilledButton.icon(
|
||||
onPressed: build0(),
|
||||
icon: Icon(c.busy ? Icons.playlist_add : Icons.play_arrow, size: 20),
|
||||
label: Text(trf('MKV 만들기{0}{1}', [count, c.busy ? tr(' (대기열)') : ''])),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.add, tr('동영상 추가'), c.pickVideos, text: true, tip: addTip),
|
||||
if (hasPlayer) ...[
|
||||
const SizedBox(width: 8),
|
||||
action(
|
||||
Icons.playlist_play,
|
||||
trf('선택한 파일 재생{0}', [count]),
|
||||
batch.isEmpty ? null : () => playFiles(context, c, [for (final v in batch) v.path], keepOrder: true),
|
||||
text: true,
|
||||
tip: tr('선택한 파일 재생: 지금 보고 있는 동영상 (여러 개는 목록에서 체크)')),
|
||||
],
|
||||
if (hasAi) ...[
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.auto_awesome, trf('자막 만들기{0}', [count]),
|
||||
canBatch ? () => showAiDialog(context, c, batch.first, targets: batch) : null,
|
||||
text: true, tip: trf('자막 만들기: {0}', [_batchHint(c, tr('AI 자막을 만듭니다'))])),
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.auto_mode, trf('자막 만들기 & MKV 만들기{0}', [count]),
|
||||
canBatch ? () => showAiDialog(context, c, batch.first, targets: batch, thenBuild: true) : null,
|
||||
text: true,
|
||||
tip: trf('자막 만들기 & MKV 만들기: {0}', [_batchHint(c, tr('AI 자막을 만들고 이어서 MKV 로 만듭니다'))])),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.folder_special_outlined, tr('결과 폴더'), c.selected == null ? null : () => _openOutput(context),
|
||||
text: true, tip: tr('결과 폴더 열기')),
|
||||
if (c.busy) ...[
|
||||
const SizedBox(width: 8),
|
||||
JobIndicator(c: c, compact: true, iconOnly: false),
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.stop, tr('취소'), c.cancel, text: true, tip: tr('작업 취소'), color: JjColors.danger),
|
||||
],
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AppTopBar(
|
||||
// 모든 화면 공통: [JJ 홈] [MKV 화면] [뒤로] [다운로드 목록]
|
||||
nav: const AppNavButtons(),
|
||||
|
|
@ -225,7 +288,7 @@ class _TopBar extends StatelessWidget {
|
|||
scrollDirection: Axis.horizontal,
|
||||
child: Row(children: [
|
||||
action(Icons.add, tr('동영상 추가'), c.pickVideos,
|
||||
text: leftText, tip: tr('동영상 추가 (탐색기에서 끌어다 놓아도 됩니다)')),
|
||||
text: leftText, tip: addTip),
|
||||
if (hasAi) ...[
|
||||
const SizedBox(width: 8),
|
||||
action(Icons.auto_awesome, trf('자막 만들기{0}', [count]),
|
||||
|
|
@ -423,11 +486,23 @@ class _EncodeBar extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
]);
|
||||
if (compact) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
color: JjColors.panel,
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 6),
|
||||
child: Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runSpacing: 6,
|
||||
children: [for (final w in row.children) if (w is! Expanded) w],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
height: 44,
|
||||
color: JjColors.panel,
|
||||
padding: EdgeInsets.symmetric(horizontal: compact ? 8 : 16),
|
||||
child: compact ? SingleChildScrollView(scrollDirection: Axis.horizontal, child: row) : row,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: row,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -697,21 +772,28 @@ class _StatusIcon extends StatelessWidget {
|
|||
// ───────── 오른쪽: 선택한 동영상 ─────────
|
||||
|
||||
class _EmptyHint extends StatelessWidget {
|
||||
const _EmptyHint();
|
||||
final VoidCallback? onAdd;
|
||||
const _EmptyHint({this.onAdd});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.video_library_outlined, size: 56, color: JjColors.textDim),
|
||||
SizedBox(height: 12),
|
||||
Text(tr('"동영상 추가" 로 파일을 선택하세요'),
|
||||
style: TextStyle(color: JjColors.textDim)),
|
||||
SizedBox(height: 4),
|
||||
const SizedBox(height: 12),
|
||||
Text(tr('"동영상 추가" 로 파일을 선택하세요'), textAlign: TextAlign.center, style: const TextStyle(color: JjColors.textDim)),
|
||||
const SizedBox(height: 4),
|
||||
Text(tr('같은 폴더의 자막은 자동으로 추가됩니다'),
|
||||
style: TextStyle(color: JjColors.textDim, fontSize: 12)),
|
||||
textAlign: TextAlign.center, style: const TextStyle(color: JjColors.textDim, fontSize: 12)),
|
||||
if (onAdd != null) ...[
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(onPressed: onAdd, icon: const Icon(Icons.add), label: Text(tr('동영상 추가'))),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -384,10 +384,12 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
builder: (_, s, _) {
|
||||
final total = s.duration.inMilliseconds.toDouble();
|
||||
final pos = (_seekDrag ?? s.position.inMilliseconds.toDouble()).clamp(0.0, total <= 0 ? 1.0 : total);
|
||||
// 9: 손가락 (Android) 은 버튼을 크게 · 넉넉하게 (작고 촘촘해 누르기 어려웠던 것)
|
||||
final touch = Platform.isAndroid;
|
||||
IconButton btn(IconData i, String tip, VoidCallback onTap, {double size = 22}) => IconButton(
|
||||
tooltip: tip,
|
||||
iconSize: size,
|
||||
visualDensity: VisualDensity.compact,
|
||||
iconSize: touch ? size + 6 : size,
|
||||
visualDensity: touch ? VisualDensity.standard : VisualDensity.compact,
|
||||
color: Colors.white,
|
||||
onPressed: onTap,
|
||||
icon: Icon(i),
|
||||
|
|
@ -416,7 +418,19 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
Text(formatSrtTime(s.duration).substring(0, 8),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white70, fontFamily: 'Consolas')),
|
||||
]),
|
||||
// 손가락: 재생 버튼 줄과 나머지 (음량 · 자막 · 목록 · 전체 화면) 줄을 나눠 넉넉하게
|
||||
if (touch)
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
btn(Icons.skip_previous, tr('이전 (P)'), () => pl.previous()),
|
||||
btn(Icons.replay_10, tr('10초 뒤로 (←)'), () => _seekBy(-10)),
|
||||
btn(s.playing ? Icons.pause_circle_filled : Icons.play_circle_fill, tr('재생/일시정지 (Space)'),
|
||||
() => pl.playOrPause(), size: 40),
|
||||
btn(Icons.stop, tr('정지'), () => pl.stop()),
|
||||
btn(Icons.forward_10, tr('10초 앞으로 (→)'), () => _seekBy(10)),
|
||||
btn(Icons.skip_next, tr('다음 (N)'), () => pl.next()),
|
||||
]),
|
||||
Row(children: [
|
||||
if (!touch) ...[
|
||||
btn(Icons.skip_previous, tr('이전 (P)'), () => pl.previous()),
|
||||
if (!compact) btn(Icons.replay_10, tr('10초 뒤로 (←)'), () => _seekBy(-10)),
|
||||
btn(s.playing ? Icons.pause_circle_filled : Icons.play_circle_fill, tr('재생/일시정지 (Space)'),
|
||||
|
|
@ -425,6 +439,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
if (!compact) btn(Icons.forward_10, tr('10초 앞으로 (→)'), () => _seekBy(10)),
|
||||
btn(Icons.skip_next, tr('다음 (N)'), () => pl.next()),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
btn(s.volume == 0 ? Icons.volume_off : Icons.volume_up, tr('음소거 (M)'), _toggleMute),
|
||||
if (!compact)
|
||||
SizedBox(
|
||||
|
|
@ -463,7 +478,7 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
List<({String label, bool checked, VoidCallback onTap})> items) =>
|
||||
PopupMenuButton<int>(
|
||||
tooltip: tip,
|
||||
icon: Icon(icon, color: Colors.white, size: 22),
|
||||
icon: Icon(icon, color: Colors.white, size: Platform.isAndroid ? 28 : 22),
|
||||
itemBuilder: (_) => [
|
||||
for (var i = 0; i < items.length; i++)
|
||||
CheckedPopupMenuItem(value: i, checked: items[i].checked, child: Text(items[i].label)),
|
||||
|
|
|
|||
|
|
@ -492,6 +492,12 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
title: Text(tr('YouTube 광고 배너 숨기기')),
|
||||
subtitle: Text(tr('목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.webPauseOnLeave,
|
||||
onChanged: (v) => c.updateSettings((x) => x.webPauseOnLeave = v),
|
||||
title: Text(tr('다른 화면으로 가면 페이지 소리 멈춤')),
|
||||
subtitle: Text(tr('끄면 환경 설정 · 다운로드 목록 · 다른 화면을 보는 동안에도 웹 페이지의 소리가 계속 들립니다')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.webTranslate,
|
||||
onChanged: (v) => c.updateSettings((x) => x.webTranslate = v),
|
||||
|
|
|
|||
|
|
@ -120,13 +120,13 @@ void main() {
|
|||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(MaterialApp(home: HomePage(c: c, onExit: () {})));
|
||||
expect(find.text('동영상 추가'), findsOneWidget);
|
||||
expect(find.text('동영상 추가'), findsWidgets);
|
||||
await tester.runAsync(() => i18n.apply('en'));
|
||||
await tester.pump();
|
||||
expect(find.text('Add videos'), findsOneWidget);
|
||||
expect(find.text('Add videos'), findsWidgets);
|
||||
expect(find.text('동영상 추가'), findsNothing);
|
||||
await tester.runAsync(() => i18n.apply('zh-Hans'));
|
||||
await tester.pump();
|
||||
expect(find.text('添加视频'), findsOneWidget);
|
||||
expect(find.text('添加视频'), findsWidgets);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue