WIP MKV 화면 (15 지적 ①②③): 고른 것 없음 안내 · 폰 세로 버튼 줄 [›] · 목록은 내용만큼 + 설정 "폰 세로 목록 크기" (검토 대기)
① 동영상이 있는데 고른 것이 없으면 빈 목록 안내 ("동영상 추가로 파일을 선택하세요") 대신
"고른 동영상이 없습니다 · 목록에서 동영상을 누르면 여기에 자세히 보입니다" (넓은 · 좁은 화면 모두).
첫 줄 자동 고르기는 하지 않음 (관리자 판단).
② 폰 세로 (폭 412) 위 버튼 줄이 화면보다 길면 끝에 흐림 · [›] (밀었으면 [‹]), 누르면 한 화면쯤 민다
(새 위젯 ScrollMore). 예전에는 세 번째 버튼이 잘린 채 더 있다는 표시가 없었다 (버그).
③ 폰 세로 목록 높이: 화면 32% 고정 → 관리자 판단대로 목록은 내용만큼, 자세히 칸 최소 높이를 남기고
남는 공간은 목록에. 설정 "폰 세로 목록 크기" 자동 (처음 값) / 작게 (25%) / 크게 (55%), 모르는 값은 자동.
- 내 판단: 자동의 자세히 칸 최소 = 고른 동영상이 있으면 62% (261 · 266 의 자막 버튼 보호, 예전 68% 와 비슷),
없으면 안내 자리 120.
- 내 판단: 작업 기록 칸이 비었으면 "작업 기록이 아직 없습니다 …" 를 적는다 (✕ 만 있는 빈 칸으로 보였다, 높이는 그대로).
- 시험 mkv_compact_list_test (412 · 1280, 작게 < 크게 < 자동, [›] 누르기, 빈 기록 안내), 번역 en · ja · zh-Hans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음6
parent
a2cb535f1e
commit
8833c93bf8
|
|
@ -604,6 +604,7 @@
|
||||||
"고르지 않음": "Not chosen",
|
"고르지 않음": "Not chosen",
|
||||||
"고른 것 시작": "Start selected",
|
"고른 것 시작": "Start selected",
|
||||||
"고른 것 풀기": "Extract selected",
|
"고른 것 풀기": "Extract selected",
|
||||||
|
"고른 동영상이 없습니다": "No video selected",
|
||||||
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "Best quality at or below the chosen size (nearest if the video lacks that size)",
|
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "Best quality at or below the chosen size (nearest if the video lacks that size)",
|
||||||
"고지 문서": "Notices",
|
"고지 문서": "Notices",
|
||||||
"고치기": "Edit",
|
"고치기": "Edit",
|
||||||
|
|
@ -1019,6 +1020,7 @@
|
||||||
"목록": "list",
|
"목록": "list",
|
||||||
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "Hides ad areas in lists · next to · on top of videos",
|
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "Hides ad areas in lists · next to · on top of videos",
|
||||||
"목록 보기": "View list",
|
"목록 보기": "View list",
|
||||||
|
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "Tap a video in the list to see its details here",
|
||||||
"목록에서 빼기": "Remove from list",
|
"목록에서 빼기": "Remove from list",
|
||||||
"목록에서 빼기 (파일은 그대로)": "Remove from list (file is kept)",
|
"목록에서 빼기 (파일은 그대로)": "Remove from list (file is kept)",
|
||||||
"목록에서 제거": "Remove from list",
|
"목록에서 제거": "Remove from list",
|
||||||
|
|
@ -1113,6 +1115,7 @@
|
||||||
"버전 목록을 읽을 수 없습니다 ({0})": "Cannot read the version list ({0})",
|
"버전 목록을 읽을 수 없습니다 ({0})": "Cannot read the version list ({0})",
|
||||||
"버전 목록을 읽을 수 없습니다: {0}": "Cannot read the version list: {0}",
|
"버전 목록을 읽을 수 없습니다: {0}": "Cannot read the version list: {0}",
|
||||||
"버튼 구성": "Button layout",
|
"버튼 구성": "Button layout",
|
||||||
|
"버튼 더 보기": "More buttons",
|
||||||
"번역 모델 내려받는 중 {0}%": "Downloading translation model {0}%",
|
"번역 모델 내려받는 중 {0}%": "Downloading translation model {0}%",
|
||||||
"번역 모델 내려받는 중: {0}": "Downloading translation model: {0}",
|
"번역 모델 내려받는 중: {0}": "Downloading translation model: {0}",
|
||||||
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "Translation model license: CC BY-NC 4.0 (non-commercial use only)",
|
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "Translation model license: CC BY-NC 4.0 (non-commercial use only)",
|
||||||
|
|
@ -1411,6 +1414,7 @@
|
||||||
"압축을 풀 수 없습니다: {0}": "Cannot extract the archive: {0}",
|
"압축을 풀 수 없습니다: {0}": "Cannot extract the archive: {0}",
|
||||||
"앞으로": "Forward",
|
"앞으로": "Forward",
|
||||||
"앞의 rsync 가 끝난 뒤에 하세요.": "Wait until the previous rsync finishes.",
|
"앞의 rsync 가 끝난 뒤에 하세요.": "Wait until the previous rsync finishes.",
|
||||||
|
"앞의 버튼 보기": "Previous buttons",
|
||||||
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "Wait until the current copy · move finishes.",
|
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "Wait until the current copy · move finishes.",
|
||||||
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "Icon in the app list, home screen and the top-left button in the app. After changing it, the icon on your home screen may disappear and you may need to drag it from the app list again",
|
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "Icon in the app list, home screen and the top-left button in the app. After changing it, the icon on your home screen may disappear and you may need to drag it from the app list again",
|
||||||
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "If a list of apps opened, find and tap JJ_MKVMaker in the list, turn on \"All files access\", and come back.",
|
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "If a list of apps opened, find and tap JJ_MKVMaker in the list, turn on \"All files access\", and come back.",
|
||||||
|
|
@ -1819,10 +1823,12 @@
|
||||||
"자세히": "Details",
|
"자세히": "Details",
|
||||||
"자세히 닫기": "Hide details",
|
"자세히 닫기": "Hide details",
|
||||||
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "For a home NAS with a self-signed certificate, etc. (trusted servers only)",
|
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "For a home NAS with a self-signed certificate, etc. (trusted servers only)",
|
||||||
|
"작게": "Small",
|
||||||
"작업": "Jobs",
|
"작업": "Jobs",
|
||||||
"작업 기록 · 받다 만 파일": "Activity log · unfinished downloads",
|
"작업 기록 · 받다 만 파일": "Activity log · unfinished downloads",
|
||||||
"작업 기록 보기": "Show job log",
|
"작업 기록 보기": "Show job log",
|
||||||
"작업 기록 숨기기": "Hide job log",
|
"작업 기록 숨기기": "Hide job log",
|
||||||
|
"작업 기록이 아직 없습니다. MKV 만들기 · 자막 · 다운로드를 하면 여기에 차례로 남습니다.": "No activity yet. MKV builds, subtitles and downloads will be logged here in order.",
|
||||||
"작업 임시 파일": "Job temporary files",
|
"작업 임시 파일": "Job temporary files",
|
||||||
"작업 중": "Working",
|
"작업 중": "Working",
|
||||||
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
||||||
|
|
@ -1917,6 +1923,7 @@
|
||||||
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "Set. Asked when using saved passwords, settings or features that need a password (once until the app is closed)",
|
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "Set. Asked when using saved passwords, settings or features that need a password (once until the app is closed)",
|
||||||
"제목": "Title",
|
"제목": "Title",
|
||||||
"조정 전": "Before",
|
"조정 전": "Before",
|
||||||
|
"좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "Height of the video list on narrow screens (Auto: as tall as its rows, keeping room for the details)",
|
||||||
"종료": "Quit",
|
"종료": "Quit",
|
||||||
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "When you press quit (close window ✕ · quit button)",
|
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "When you press quit (close window ✕ · quit button)",
|
||||||
"종료 시작 {0}": "Quit started {0}",
|
"종료 시작 {0}": "Quit started {0}",
|
||||||
|
|
@ -2111,6 +2118,7 @@
|
||||||
"쿠키 · 방문 기록 지우기": "Clear cookies and history",
|
"쿠키 · 방문 기록 지우기": "Clear cookies and history",
|
||||||
"쿠키 · 방문 기록을 지웠습니다.": "Cleared cookies and history.",
|
"쿠키 · 방문 기록을 지웠습니다.": "Cleared cookies and history.",
|
||||||
"쿠키 파일 지우기": "Delete cookie file",
|
"쿠키 파일 지우기": "Delete cookie file",
|
||||||
|
"크게": "Large",
|
||||||
"크기": "Size",
|
"크기": "Size",
|
||||||
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "Size and adjustments are back to original, so the codec was set back to \"Keep original\".",
|
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "Size and adjustments are back to original, so the codec was set back to \"Keep original\".",
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "Changing the size requires re-encoding, so the codec was changed from \"Keep original\" to H.264.",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "Changing the size requires re-encoding, so the codec was changed from \"Keep original\" to H.264.",
|
||||||
|
|
@ -2182,6 +2190,7 @@
|
||||||
"포르투갈어": "Portuguese",
|
"포르투갈어": "Portuguese",
|
||||||
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "Included components and licenses (with full GPL · LGPL texts)",
|
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "Included components and licenses (with full GPL · LGPL texts)",
|
||||||
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "Included components and licenses (THIRD_PARTY_NOTICES.txt)",
|
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "Included components and licenses (THIRD_PARTY_NOTICES.txt)",
|
||||||
|
"폰 세로 목록 크기": "List size (portrait phone)",
|
||||||
"폴더": "Folder",
|
"폴더": "Folder",
|
||||||
"폴더 + 파일 목록": "Folders + file list",
|
"폴더 + 파일 목록": "Folders + file list",
|
||||||
"폴더 {0}개 (크기 빼고)": "{0} folders (size not counted)",
|
"폴더 {0}개 (크기 빼고)": "{0} folders (size not counted)",
|
||||||
|
|
|
||||||
|
|
@ -604,6 +604,7 @@
|
||||||
"고르지 않음": "未選択",
|
"고르지 않음": "未選択",
|
||||||
"고른 것 시작": "選んだものを開始",
|
"고른 것 시작": "選んだものを開始",
|
||||||
"고른 것 풀기": "選択したものを展開",
|
"고른 것 풀기": "選択したものを展開",
|
||||||
|
"고른 동영상이 없습니다": "動画が選択されていません",
|
||||||
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "選んだサイズ以下で最も良い画質(動画にないサイズなら近いもの)",
|
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "選んだサイズ以下で最も良い画質(動画にないサイズなら近いもの)",
|
||||||
"고지 문서": "告知文書",
|
"고지 문서": "告知文書",
|
||||||
"고치기": "編集",
|
"고치기": "編集",
|
||||||
|
|
@ -1019,6 +1020,7 @@
|
||||||
"목록": "一覧",
|
"목록": "一覧",
|
||||||
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "リスト・動画の横・動画の上に出る広告エリアを隠します",
|
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "リスト・動画の横・動画の上に出る広告エリアを隠します",
|
||||||
"목록 보기": "リストを見る",
|
"목록 보기": "リストを見る",
|
||||||
|
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "リストの動画をタップすると、ここに詳細が表示されます",
|
||||||
"목록에서 빼기": "リストから外す",
|
"목록에서 빼기": "リストから外す",
|
||||||
"목록에서 빼기 (파일은 그대로)": "リストから外す (ファイルはそのまま)",
|
"목록에서 빼기 (파일은 그대로)": "リストから外す (ファイルはそのまま)",
|
||||||
"목록에서 제거": "リストから削除",
|
"목록에서 제거": "リストから削除",
|
||||||
|
|
@ -1113,6 +1115,7 @@
|
||||||
"버전 목록을 읽을 수 없습니다 ({0})": "バージョン一覧を読めません ({0})",
|
"버전 목록을 읽을 수 없습니다 ({0})": "バージョン一覧を読めません ({0})",
|
||||||
"버전 목록을 읽을 수 없습니다: {0}": "バージョン一覧を読めません: {0}",
|
"버전 목록을 읽을 수 없습니다: {0}": "バージョン一覧を読めません: {0}",
|
||||||
"버튼 구성": "ボタン構成",
|
"버튼 구성": "ボタン構成",
|
||||||
|
"버튼 더 보기": "ほかのボタンを表示",
|
||||||
"번역 모델 내려받는 중 {0}%": "翻訳モデルをダウンロード中 {0}%",
|
"번역 모델 내려받는 중 {0}%": "翻訳モデルをダウンロード中 {0}%",
|
||||||
"번역 모델 내려받는 중: {0}": "翻訳モデルをダウンロード中: {0}",
|
"번역 모델 내려받는 중: {0}": "翻訳モデルをダウンロード中: {0}",
|
||||||
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "翻訳モデルのライセンス: CC BY-NC 4.0 (非営利利用のみ)",
|
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "翻訳モデルのライセンス: CC BY-NC 4.0 (非営利利用のみ)",
|
||||||
|
|
@ -1411,6 +1414,7 @@
|
||||||
"압축을 풀 수 없습니다: {0}": "圧縮ファイルを展開できません: {0}",
|
"압축을 풀 수 없습니다: {0}": "圧縮ファイルを展開できません: {0}",
|
||||||
"앞으로": "進む",
|
"앞으로": "進む",
|
||||||
"앞의 rsync 가 끝난 뒤에 하세요.": "前の rsync が終わってから行ってください。",
|
"앞의 rsync 가 끝난 뒤에 하세요.": "前の rsync が終わってから行ってください。",
|
||||||
|
"앞의 버튼 보기": "前のボタンを表示",
|
||||||
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "前のコピー · 移動が終わってから行ってください。",
|
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "前のコピー · 移動が終わってから行ってください。",
|
||||||
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "アプリ一覧・ホーム画面とアプリ内左上のボタンのアイコンです。変更するとホーム画面のアイコンが消えることがあり、アプリ一覧から再度ドラッグする必要がある場合があります",
|
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "アプリ一覧・ホーム画面とアプリ内左上のボタンのアイコンです。変更するとホーム画面のアイコンが消えることがあり、アプリ一覧から再度ドラッグする必要がある場合があります",
|
||||||
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "アプリ一覧が開いた場合は、一覧から JJ_MKVMaker を探してタップし、「すべてのファイルへのアクセス」をオンにして戻ってください。",
|
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "アプリ一覧が開いた場合は、一覧から JJ_MKVMaker を探してタップし、「すべてのファイルへのアクセス」をオンにして戻ってください。",
|
||||||
|
|
@ -1819,10 +1823,12 @@
|
||||||
"자세히": "詳細",
|
"자세히": "詳細",
|
||||||
"자세히 닫기": "詳細を閉じる",
|
"자세히 닫기": "詳細を閉じる",
|
||||||
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "自己署名証明書を使う自宅 NAS など (信頼できるサーバーのみ)",
|
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "自己署名証明書を使う自宅 NAS など (信頼できるサーバーのみ)",
|
||||||
|
"작게": "小さく",
|
||||||
"작업": "作業",
|
"작업": "作業",
|
||||||
"작업 기록 · 받다 만 파일": "作業記録・ダウンロード途中のファイル",
|
"작업 기록 · 받다 만 파일": "作業記録・ダウンロード途中のファイル",
|
||||||
"작업 기록 보기": "作業記録を表示",
|
"작업 기록 보기": "作業記録を表示",
|
||||||
"작업 기록 숨기기": "作業記録を隠す",
|
"작업 기록 숨기기": "作業記録を隠す",
|
||||||
|
"작업 기록이 아직 없습니다. MKV 만들기 · 자막 · 다운로드를 하면 여기에 차례로 남습니다.": "作業記録はまだありません。MKV 作成・字幕・ダウンロードを行うと、ここに順に記録されます。",
|
||||||
"작업 임시 파일": "作業の一時ファイル",
|
"작업 임시 파일": "作業の一時ファイル",
|
||||||
"작업 중": "作業中",
|
"작업 중": "作業中",
|
||||||
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
||||||
|
|
@ -1917,6 +1923,7 @@
|
||||||
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "設定済み。保存したパスワード・設定・パスワードが必要な機能を使うときに尋ねます (アプリを終了するまで 1 回)",
|
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "設定済み。保存したパスワード・設定・パスワードが必要な機能を使うときに尋ねます (アプリを終了するまで 1 回)",
|
||||||
"제목": "タイトル",
|
"제목": "タイトル",
|
||||||
"조정 전": "調整前",
|
"조정 전": "調整前",
|
||||||
|
"좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "狭い画面で動画リストが占める高さ (自動: 行の数だけ、詳細欄の場所は残す)",
|
||||||
"종료": "終了",
|
"종료": "終了",
|
||||||
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "終了(ウィンドウを閉じる ✕・終了ボタン)を押したとき",
|
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "終了(ウィンドウを閉じる ✕・終了ボタン)を押したとき",
|
||||||
"종료 시작 {0}": "終了開始 {0}",
|
"종료 시작 {0}": "終了開始 {0}",
|
||||||
|
|
@ -2111,6 +2118,7 @@
|
||||||
"쿠키 · 방문 기록 지우기": "Cookie・閲覧履歴を消去",
|
"쿠키 · 방문 기록 지우기": "Cookie・閲覧履歴を消去",
|
||||||
"쿠키 · 방문 기록을 지웠습니다.": "Cookie・閲覧履歴を消去しました。",
|
"쿠키 · 방문 기록을 지웠습니다.": "Cookie・閲覧履歴を消去しました。",
|
||||||
"쿠키 파일 지우기": "Cookie ファイルを削除",
|
"쿠키 파일 지우기": "Cookie ファイルを削除",
|
||||||
|
"크게": "大きく",
|
||||||
"크기": "サイズ",
|
"크기": "サイズ",
|
||||||
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "サイズ・補正を元に戻したので、コーデックも「元のまま」に戻しました。",
|
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "サイズ・補正を元に戻したので、コーデックも「元のまま」に戻しました。",
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "サイズを変えると再エンコードが必要なため、コーデックを「元のまま」から H.264 に変更しました。",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "サイズを変えると再エンコードが必要なため、コーデックを「元のまま」から H.264 に変更しました。",
|
||||||
|
|
@ -2182,6 +2190,7 @@
|
||||||
"포르투갈어": "ポルトガル語",
|
"포르투갈어": "ポルトガル語",
|
||||||
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "含まれるコンポーネントとライセンス (GPL・LGPL 全文を含む)",
|
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "含まれるコンポーネントとライセンス (GPL・LGPL 全文を含む)",
|
||||||
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "含まれるコンポーネントとライセンス(THIRD_PARTY_NOTICES.txt)",
|
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "含まれるコンポーネントとライセンス(THIRD_PARTY_NOTICES.txt)",
|
||||||
|
"폰 세로 목록 크기": "縦向きスマホのリストの大きさ",
|
||||||
"폴더": "フォルダー",
|
"폴더": "フォルダー",
|
||||||
"폴더 + 파일 목록": "フォルダー + ファイル一覧",
|
"폴더 + 파일 목록": "フォルダー + ファイル一覧",
|
||||||
"폴더 {0}개 (크기 빼고)": "フォルダー {0} 件 (サイズ除く)",
|
"폴더 {0}개 (크기 빼고)": "フォルダー {0} 件 (サイズ除く)",
|
||||||
|
|
|
||||||
|
|
@ -604,6 +604,7 @@
|
||||||
"고르지 않음": "未选择",
|
"고르지 않음": "未选择",
|
||||||
"고른 것 시작": "开始所选",
|
"고른 것 시작": "开始所选",
|
||||||
"고른 것 풀기": "解压所选",
|
"고른 것 풀기": "解压所选",
|
||||||
|
"고른 동영상이 없습니다": "未选择视频",
|
||||||
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "不超过所选尺寸的最佳画质(视频没有该尺寸时取最接近的)",
|
"고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)": "不超过所选尺寸的最佳画质(视频没有该尺寸时取最接近的)",
|
||||||
"고지 문서": "声明文件",
|
"고지 문서": "声明文件",
|
||||||
"고치기": "编辑",
|
"고치기": "编辑",
|
||||||
|
|
@ -1019,6 +1020,7 @@
|
||||||
"목록": "列表",
|
"목록": "列表",
|
||||||
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "隐藏列表 · 视频旁 · 视频上方出现的广告区域",
|
"목록 · 영상 옆 · 영상 위에 나오는 광고 영역을 감춥니다": "隐藏列表 · 视频旁 · 视频上方出现的广告区域",
|
||||||
"목록 보기": "查看列表",
|
"목록 보기": "查看列表",
|
||||||
|
"목록에서 동영상을 누르면 여기에 자세히 보입니다": "点按列表中的视频即可在此查看详情",
|
||||||
"목록에서 빼기": "从列表中移除",
|
"목록에서 빼기": "从列表中移除",
|
||||||
"목록에서 빼기 (파일은 그대로)": "从列表中移除 (保留文件)",
|
"목록에서 빼기 (파일은 그대로)": "从列表中移除 (保留文件)",
|
||||||
"목록에서 제거": "从列表中移除",
|
"목록에서 제거": "从列表中移除",
|
||||||
|
|
@ -1113,6 +1115,7 @@
|
||||||
"버전 목록을 읽을 수 없습니다 ({0})": "无法读取版本列表 ({0})",
|
"버전 목록을 읽을 수 없습니다 ({0})": "无法读取版本列表 ({0})",
|
||||||
"버전 목록을 읽을 수 없습니다: {0}": "无法读取版本列表: {0}",
|
"버전 목록을 읽을 수 없습니다: {0}": "无法读取版本列表: {0}",
|
||||||
"버튼 구성": "按钮配置",
|
"버튼 구성": "按钮配置",
|
||||||
|
"버튼 더 보기": "更多按钮",
|
||||||
"번역 모델 내려받는 중 {0}%": "正在下载翻译模型 {0}%",
|
"번역 모델 내려받는 중 {0}%": "正在下载翻译模型 {0}%",
|
||||||
"번역 모델 내려받는 중: {0}": "正在下载翻译模型: {0}",
|
"번역 모델 내려받는 중: {0}": "正在下载翻译模型: {0}",
|
||||||
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "翻译模型许可证:CC BY-NC 4.0(仅限非商业用途)",
|
"번역 모델 라이선스: CC BY-NC 4.0 (비상업적 이용만 허용)": "翻译模型许可证:CC BY-NC 4.0(仅限非商业用途)",
|
||||||
|
|
@ -1411,6 +1414,7 @@
|
||||||
"압축을 풀 수 없습니다: {0}": "无法解压: {0}",
|
"압축을 풀 수 없습니다: {0}": "无法解压: {0}",
|
||||||
"앞으로": "前进",
|
"앞으로": "前进",
|
||||||
"앞의 rsync 가 끝난 뒤에 하세요.": "请等上一个 rsync 结束后再运行。",
|
"앞의 rsync 가 끝난 뒤에 하세요.": "请等上一个 rsync 结束后再运行。",
|
||||||
|
"앞의 버튼 보기": "前面的按钮",
|
||||||
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "请等待前一个复制 · 移动完成。",
|
"앞의 복사 · 이동이 끝난 뒤에 하세요.": "请等待前一个复制 · 移动完成。",
|
||||||
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "应用列表、主屏幕以及应用内左上角按钮的图标。更改后主屏幕上的图标可能会消失,可能需要从应用列表重新拖放",
|
"앱 목록 · 홈 화면과 앱 안 왼쪽 위 버튼의 아이콘입니다. 바꾸면 홈 화면에 둔 아이콘이 없어질 수 있어 앱 목록에서 다시 끌어다 놓아야 할 수 있습니다": "应用列表、主屏幕以及应用内左上角按钮的图标。更改后主屏幕上的图标可能会消失,可能需要从应用列表重新拖放",
|
||||||
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "如果打开的是应用列表,请在列表中找到并点按 JJ_MKVMaker,开启“所有文件访问权限”后返回。",
|
"앱 목록이 열렸다면, 목록에서 JJ_MKVMaker 를 찾아 눌러 \"모든 파일에 대한 접근\" 을 켜고 돌아오세요.": "如果打开的是应用列表,请在列表中找到并点按 JJ_MKVMaker,开启“所有文件访问权限”后返回。",
|
||||||
|
|
@ -1819,10 +1823,12 @@
|
||||||
"자세히": "详情",
|
"자세히": "详情",
|
||||||
"자세히 닫기": "收起详情",
|
"자세히 닫기": "收起详情",
|
||||||
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "用于使用自签名证书的家用 NAS 等 (仅限可信服务器)",
|
"자체 서명 인증서를 쓰는 집 NAS 등 (믿을 수 있는 서버에서만)": "用于使用自签名证书的家用 NAS 等 (仅限可信服务器)",
|
||||||
|
"작게": "小",
|
||||||
"작업": "任务",
|
"작업": "任务",
|
||||||
"작업 기록 · 받다 만 파일": "操作记录 · 未下载完的文件",
|
"작업 기록 · 받다 만 파일": "操作记录 · 未下载完的文件",
|
||||||
"작업 기록 보기": "显示任务记录",
|
"작업 기록 보기": "显示任务记录",
|
||||||
"작업 기록 숨기기": "隐藏任务记录",
|
"작업 기록 숨기기": "隐藏任务记录",
|
||||||
|
"작업 기록이 아직 없습니다. MKV 만들기 · 자막 · 다운로드를 하면 여기에 차례로 남습니다.": "暂无工作记录。生成 MKV、字幕和下载后会按顺序记录在这里。",
|
||||||
"작업 임시 파일": "任务临时文件",
|
"작업 임시 파일": "任务临时文件",
|
||||||
"작업 중": "处理中",
|
"작업 중": "处理中",
|
||||||
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
||||||
|
|
@ -1917,6 +1923,7 @@
|
||||||
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "已设置。使用已保存的密码 · 设置 · 需要密码的功能时询问 (在关闭应用前只问一次)",
|
"정해 둠. 저장된 비밀번호 · 설정 · 비밀번호가 필요한 기능을 쓸 때 묻습니다 (앱을 끌 때까지 한 번)": "已设置。使用已保存的密码 · 设置 · 需要密码的功能时询问 (在关闭应用前只问一次)",
|
||||||
"제목": "标题",
|
"제목": "标题",
|
||||||
"조정 전": "调整前",
|
"조정 전": "调整前",
|
||||||
|
"좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)": "窄屏时视频列表所占高度 (自动: 按行数,并为详情区留出空间)",
|
||||||
"종료": "退出",
|
"종료": "退出",
|
||||||
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "点击退出(关闭窗口 ✕ · 退出按钮)时",
|
"종료 (창 닫기 ✕ · 종료 버튼) 를 누르면": "点击退出(关闭窗口 ✕ · 退出按钮)时",
|
||||||
"종료 시작 {0}": "开始退出 {0}",
|
"종료 시작 {0}": "开始退出 {0}",
|
||||||
|
|
@ -2111,6 +2118,7 @@
|
||||||
"쿠키 · 방문 기록 지우기": "清除 Cookie · 浏览记录",
|
"쿠키 · 방문 기록 지우기": "清除 Cookie · 浏览记录",
|
||||||
"쿠키 · 방문 기록을 지웠습니다.": "已清除 Cookie · 浏览记录。",
|
"쿠키 · 방문 기록을 지웠습니다.": "已清除 Cookie · 浏览记录。",
|
||||||
"쿠키 파일 지우기": "删除 Cookie 文件",
|
"쿠키 파일 지우기": "删除 Cookie 文件",
|
||||||
|
"크게": "大",
|
||||||
"크기": "大小",
|
"크기": "大小",
|
||||||
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "大小 · 调整已恢复原样,编解码器也恢复为“保持原样”。",
|
"크기 · 보정을 원래대로 돌려 코덱도 \"원본 유지\" 로 되돌렸습니다.": "大小 · 调整已恢复原样,编解码器也恢复为“保持原样”。",
|
||||||
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "更改大小需要重新编码,因此编解码器已从“保持原样”改为 H.264。",
|
"크기를 바꾸면 다시 인코딩해야 해서 코덱을 \"원본 유지\" 에서 H.264 로 바꿨습니다.": "更改大小需要重新编码,因此编解码器已从“保持原样”改为 H.264。",
|
||||||
|
|
@ -2182,6 +2190,7 @@
|
||||||
"포르투갈어": "葡萄牙语",
|
"포르투갈어": "葡萄牙语",
|
||||||
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "包含的组件和许可证 (含 GPL · LGPL 全文)",
|
"포함된 구성 요소와 라이선스 (GPL · LGPL 원문 포함)": "包含的组件和许可证 (含 GPL · LGPL 全文)",
|
||||||
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "包含的组件与许可(THIRD_PARTY_NOTICES.txt)",
|
"포함된 구성 요소와 라이선스 (THIRD_PARTY_NOTICES.txt)": "包含的组件与许可(THIRD_PARTY_NOTICES.txt)",
|
||||||
|
"폰 세로 목록 크기": "竖屏手机列表大小",
|
||||||
"폴더": "文件夹",
|
"폴더": "文件夹",
|
||||||
"폴더 + 파일 목록": "文件夹 + 文件列表",
|
"폴더 + 파일 목록": "文件夹 + 文件列表",
|
||||||
"폴더 {0}개 (크기 빼고)": "{0} 个文件夹(不计大小)",
|
"폴더 {0}개 (크기 빼고)": "{0} 个文件夹(不计大小)",
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,9 @@ class AppSettings {
|
||||||
/// 메인 화면 아래 작업 기록 창 보이기
|
/// 메인 화면 아래 작업 기록 창 보이기
|
||||||
bool showLog = true;
|
bool showLog = true;
|
||||||
|
|
||||||
|
/// 폰 세로 (좁은 화면) MKV 목록 크기: 'auto' 목록은 내용만큼 · 자세히 칸에 최소 높이 (처음 값) · 'small' · 'large' (관리자 판단 10/17)
|
||||||
|
String mkvCompactList = 'auto';
|
||||||
|
|
||||||
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([homeUrl]), 'files' = 파일 탐색기
|
/// 처음 화면: 'home' = 지금처럼 MKV 화면 (기본), 'browser' = 웹 브라우저 ([homeUrl]), 'files' = 파일 탐색기
|
||||||
String startScreen = 'home';
|
String startScreen = 'home';
|
||||||
|
|
||||||
|
|
@ -690,6 +693,7 @@ class AppSettings {
|
||||||
'maxParallelDownloads': maxParallelDownloads,
|
'maxParallelDownloads': maxParallelDownloads,
|
||||||
'maxParallelJobs': maxParallelJobs,
|
'maxParallelJobs': maxParallelJobs,
|
||||||
'showLog': showLog,
|
'showLog': showLog,
|
||||||
|
'mkvCompactList': mkvCompactList,
|
||||||
'startScreen': startScreen,
|
'startScreen': startScreen,
|
||||||
'uiScale': uiScale,
|
'uiScale': uiScale,
|
||||||
'uiScaleDefault': uiScaleDefault,
|
'uiScaleDefault': uiScaleDefault,
|
||||||
|
|
@ -877,6 +881,7 @@ class AppSettings {
|
||||||
..maxParallelDownloads = (j['maxParallelDownloads'] as num?)?.toInt() ?? 3
|
..maxParallelDownloads = (j['maxParallelDownloads'] as num?)?.toInt() ?? 3
|
||||||
..maxParallelJobs = (j['maxParallelJobs'] as num?)?.toInt() ?? 5
|
..maxParallelJobs = (j['maxParallelJobs'] as num?)?.toInt() ?? 5
|
||||||
..showLog = j['showLog'] as bool? ?? true
|
..showLog = j['showLog'] as bool? ?? true
|
||||||
|
..mkvCompactList = const ['auto', 'small', 'large'].contains(j['mkvCompactList']) ? j['mkvCompactList'] as String : 'auto'
|
||||||
..startScreen = j['startScreen'] as String? ?? 'home'
|
..startScreen = j['startScreen'] as String? ?? 'home'
|
||||||
..uiScale = clampUiScale(j['uiScale'] as num?)
|
..uiScale = clampUiScale(j['uiScale'] as num?)
|
||||||
..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?)
|
..uiScaleDefault = clampUiScale(j['uiScaleDefault'] as num?)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ 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 'scroll_more.dart';
|
||||||
import 'downloads_page.dart';
|
import 'downloads_page.dart';
|
||||||
import 'existing_output_dialog.dart';
|
import 'existing_output_dialog.dart';
|
||||||
import 'exit_guard.dart';
|
import 'exit_guard.dart';
|
||||||
|
|
@ -71,20 +72,7 @@ class HomePage extends StatelessWidget {
|
||||||
// 8: 좁은 화면에 동영상이 없으면 빈 목록 · 안내로 나누지 않고 한 화면에 안내 + [동영상 추가]
|
// 8: 좁은 화면에 동영상이 없으면 빈 목록 · 안내로 나누지 않고 한 화면에 안내 + [동영상 추가]
|
||||||
? _EmptyHint(onAdd: c.pickVideos)
|
? _EmptyHint(onAdd: c.pickVideos)
|
||||||
: isCompact(context)
|
: isCompact(context)
|
||||||
? Column(
|
? _CompactBody(c: c)
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: (MediaQuery.sizeOf(context).height * 0.32).clamp(160.0, 420.0),
|
|
||||||
child: _VideoList(c: c)),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Expanded(
|
|
||||||
child: c.selected == null
|
|
||||||
? _EmptyHint(onAdd: c.pickVideos)
|
|
||||||
: _VideoDetail(c: c, v: c.selected!),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Row(
|
: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -94,9 +82,7 @@ class HomePage extends StatelessWidget {
|
||||||
child: _VideoList(c: c)),
|
child: _VideoList(c: c)),
|
||||||
const VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: c.selected == null
|
child: c.selected == null ? const _PickHint() : _VideoDetail(c: c, v: c.selected!),
|
||||||
? _EmptyHint(onAdd: c.pickVideos)
|
|
||||||
: _VideoDetail(c: c, v: c.selected!),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -370,8 +356,9 @@ class _TopBar extends StatelessWidget {
|
||||||
return AppTopBar(
|
return AppTopBar(
|
||||||
nav: const AppNavButtons(onMkvPage: true),
|
nav: const AppNavButtons(onMkvPage: true),
|
||||||
actions: AppActions(c: c, onExit: onExit, showUsage: false),
|
actions: AppActions(c: c, onExit: onExit, showUsage: false),
|
||||||
middle: SingleChildScrollView(
|
// 15 지적: 폭 412 에서 세 번째 버튼이 잘렸는데 더 있다는 표시가 없었다 - 끝에 흐림 · [›]
|
||||||
scrollDirection: Axis.horizontal,
|
middle: ScrollMore(
|
||||||
|
background: JjColors.panel,
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: trf('MKV 만들기{0}', [count]),
|
message: trf('MKV 만들기{0}', [count]),
|
||||||
|
|
@ -691,7 +678,10 @@ class _EncodeBar extends StatelessWidget {
|
||||||
|
|
||||||
class _VideoList extends StatelessWidget {
|
class _VideoList extends StatelessWidget {
|
||||||
final AppController c;
|
final AppController c;
|
||||||
const _VideoList({required this.c});
|
|
||||||
|
/// 좁은 화면 '자동': 줄이 적으면 내용 높이만 (빈 칸을 남기지 않게)
|
||||||
|
final bool shrink;
|
||||||
|
const _VideoList({required this.c, this.shrink = false});
|
||||||
|
|
||||||
/// 정렬 아이콘: 지금 적용된 정렬은 강조색 + 방향 화살표
|
/// 정렬 아이콘: 지금 적용된 정렬은 강조색 + 방향 화살표
|
||||||
Widget _sortButton(String by, IconData icon, String name) {
|
Widget _sortButton(String by, IconData icon, String name) {
|
||||||
|
|
@ -715,6 +705,7 @@ class _VideoList extends StatelessWidget {
|
||||||
color: JjColors.panel,
|
color: JjColors.panel,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisSize: shrink ? MainAxisSize.min : MainAxisSize.max,
|
||||||
children: [
|
children: [
|
||||||
// 전체 선택 · 선택 개수 · 선택 제거 · 모두 지우기
|
// 전체 선택 · 선택 개수 · 선택 제거 · 모두 지우기
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -775,8 +766,10 @@ class _VideoList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
Expanded(
|
Flexible(
|
||||||
|
fit: shrink ? FlexFit.loose : FlexFit.tight,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
shrinkWrap: shrink,
|
||||||
itemCount: c.videos.length,
|
itemCount: c.videos.length,
|
||||||
itemBuilder: (_, i) => _VideoTile(c: c, v: c.videos[i]),
|
itemBuilder: (_, i) => _VideoTile(c: c, v: c.videos[i]),
|
||||||
),
|
),
|
||||||
|
|
@ -985,6 +978,61 @@ class _StatusIcon extends StatelessWidget {
|
||||||
|
|
||||||
// ───────── 오른쪽: 선택한 동영상 ─────────
|
// ───────── 오른쪽: 선택한 동영상 ─────────
|
||||||
|
|
||||||
|
/// 좁은 화면 (폰 세로): 목록 위 · 자세히 아래.
|
||||||
|
/// 15 지적 · 관리자 판단 (10/17): 목록을 높이 32% 로 고정하면 줄이 적을 때 빈 칸이 남고, 많을 때 4~5줄만 보였다.
|
||||||
|
/// '자동' (처음 값) 은 목록이 내용만큼 차지하되 자세히 칸의 최소 높이를 남기고, 남는 공간은 목록에.
|
||||||
|
/// 환경 설정 "폰 세로 목록 크기" 로 작게 (25%) · 크게 (55%) 를 고를 수 있다.
|
||||||
|
class _CompactBody extends StatelessWidget {
|
||||||
|
final AppController c;
|
||||||
|
const _CompactBody({required this.c});
|
||||||
|
|
||||||
|
/// 줄 하나의 가장 작은 높이 (이보다 줄이 많으면 내용이 칸보다 길다고 보고 줄여 그리지 않는다)
|
||||||
|
static const _rowMin = 48.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => LayoutBuilder(builder: (context, box) {
|
||||||
|
final h = box.maxHeight;
|
||||||
|
final picked = c.selected != null;
|
||||||
|
final cap = switch (c.settings.mkvCompactList) {
|
||||||
|
'small' => h * 0.25,
|
||||||
|
'large' => h * 0.55,
|
||||||
|
// 자동: 자세히 칸 최소 - 고른 동영상이 있으면 62% (세부 정보 · 자막 버튼이 칸 밖으로 밀리지 않게, 261 · 266),
|
||||||
|
// 없으면 안내 한 덩이만큼
|
||||||
|
_ => h - (picked ? h * 0.62 : math.min(120.0, h * 0.4)),
|
||||||
|
}.clamp(math.min(120.0, h), h).toDouble();
|
||||||
|
// 줄이 많아 내용이 칸보다 확실히 길면 칸 높이 그대로 (shrinkWrap 은 줄을 모두 그려 느리다)
|
||||||
|
final shrink = c.settings.mkvCompactList == 'auto' && c.videos.length * _rowMin < cap;
|
||||||
|
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
|
if (shrink)
|
||||||
|
ConstrainedBox(constraints: BoxConstraints(maxHeight: cap), child: _VideoList(c: c, shrink: true))
|
||||||
|
else
|
||||||
|
SizedBox(height: cap, child: _VideoList(c: c)),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(child: picked ? _VideoDetail(c: c, v: c.selected!) : const _PickHint()),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 목록에 동영상이 있는데 고른 것이 없을 때 (15 지적: 빈 목록 안내 "동영상 추가로 파일을 고르세요" 가 나와 헷갈렸다)
|
||||||
|
class _PickHint extends StatelessWidget {
|
||||||
|
const _PickHint();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
const Icon(Icons.touch_app_outlined, size: 40, color: JjColors.textDim),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(tr('고른 동영상이 없습니다'), textAlign: TextAlign.center, style: const TextStyle(color: JjColors.textDim)),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(tr('목록에서 동영상을 누르면 여기에 자세히 보입니다'),
|
||||||
|
textAlign: TextAlign.center, style: const TextStyle(color: JjColors.textDim, fontSize: 12)),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _EmptyHint extends StatelessWidget {
|
class _EmptyHint extends StatelessWidget {
|
||||||
final VoidCallback? onAdd;
|
final VoidCallback? onAdd;
|
||||||
const _EmptyHint({this.onAdd});
|
const _EmptyHint({this.onAdd});
|
||||||
|
|
@ -1366,6 +1414,15 @@ class _LogPanel extends StatelessWidget {
|
||||||
Widget build(BuildContext context) => Container(
|
Widget build(BuildContext context) => Container(
|
||||||
color: JjColors.bg,
|
color: JjColors.bg,
|
||||||
child: Stack(children: [
|
child: Stack(children: [
|
||||||
|
// 15 지적: 기록이 없으면 ✕ 하나만 있는 빈 칸으로 보였다 - 무엇이 들어오는 칸인지 적는다
|
||||||
|
if (logs.isEmpty)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 10, 44, 8),
|
||||||
|
child: Text(tr('작업 기록이 아직 없습니다. MKV 만들기 · 자막 · 다운로드를 하면 여기에 차례로 남습니다.'),
|
||||||
|
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../l10n/tr.dart';
|
||||||
|
import 'theme.dart';
|
||||||
|
|
||||||
|
/// 옆으로 미는 줄 (폰 세로의 버튼 줄 등) 에 "더 있음" 표시.
|
||||||
|
/// 줄이 화면보다 길면 끝 쪽에 흐려지는 덮개와 [›] (왼쪽으로 밀었으면 [‹]) 를 보이고, 누르면 그쪽으로 한 화면쯤 민다.
|
||||||
|
/// (MKV 화면 폭 412: 세 번째 버튼이 화면 밖에서 잘렸는데 더 있다는 표시가 없었다 - 15 지적)
|
||||||
|
class ScrollMore extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// 덮개 색 (줄 뒤 바탕과 같게)
|
||||||
|
final Color? background;
|
||||||
|
const ScrollMore({super.key, required this.child, this.background});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScrollMore> createState() => _ScrollMoreState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScrollMoreState extends State<ScrollMore> {
|
||||||
|
final _scroll = ScrollController();
|
||||||
|
bool _left = false, _right = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_scroll.addListener(_update);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scroll.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update() {
|
||||||
|
if (!mounted || !_scroll.hasClients) return;
|
||||||
|
final p = _scroll.position;
|
||||||
|
final left = p.pixels > 1;
|
||||||
|
final right = p.pixels < p.maxScrollExtent - 1;
|
||||||
|
if (left != _left || right != _right) {
|
||||||
|
setState(() {
|
||||||
|
_left = left;
|
||||||
|
_right = right;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _jump(int dir) {
|
||||||
|
final p = _scroll.position;
|
||||||
|
final to = (p.pixels + dir * p.viewportDimension * 0.7).clamp(0.0, p.maxScrollExtent);
|
||||||
|
_scroll.animateTo(to, duration: const Duration(milliseconds: 220), curve: Curves.easeOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _edge(bool right) {
|
||||||
|
final bg = widget.background ?? Theme.of(context).scaffoldBackgroundColor;
|
||||||
|
return Positioned(
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: right ? null : 0,
|
||||||
|
right: right ? 0 : null,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: right ? Alignment.centerLeft : Alignment.centerRight,
|
||||||
|
end: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
|
colors: [bg.withValues(alpha: 0), bg.withValues(alpha: 0.95), bg],
|
||||||
|
stops: const [0, 0.45, 1],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: right ? 18 : 0, right: right ? 0 : 18),
|
||||||
|
child: Center(
|
||||||
|
child: IconButton(
|
||||||
|
tooltip: right ? tr('버튼 더 보기') : tr('앞의 버튼 보기'),
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
icon: Icon(right ? Icons.chevron_right : Icons.chevron_left, color: JjColors.accent),
|
||||||
|
onPressed: () => _jump(right ? 1 : -1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => NotificationListener<ScrollMetricsNotification>(
|
||||||
|
// 줄 안의 버튼 글이 바뀌어 길이가 달라져도 다시 본다
|
||||||
|
onNotification: (_) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _update());
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: Stack(children: [
|
||||||
|
SingleChildScrollView(controller: _scroll, scrollDirection: Axis.horizontal, child: widget.child),
|
||||||
|
if (_left) _edge(false),
|
||||||
|
if (_right) _edge(true),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -486,6 +486,20 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||||
onChanged: (v) => c.updateSettings((x) => x.mkvExisting = v ?? 'ask'),
|
onChanged: (v) => c.updateSettings((x) => x.mkvExisting = v ?? 'ask'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// 15 지적 · 관리자 판단 (10/17): 폰 세로에서 목록 · 자세히 칸 나누기
|
||||||
|
SettingTile(
|
||||||
|
title: Text(tr('폰 세로 목록 크기')),
|
||||||
|
subtitle: Text(tr('좁은 화면에서 동영상 목록이 차지하는 높이 (자동: 목록은 줄 수만큼, 자세히 칸 자리는 남김)')),
|
||||||
|
trailing: DropdownButton<String>(
|
||||||
|
value: s.mkvCompactList,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(value: 'auto', child: Text(tr('자동 (기본)'))),
|
||||||
|
DropdownMenuItem(value: 'small', child: Text(tr('작게'))),
|
||||||
|
DropdownMenuItem(value: 'large', child: Text(tr('크게'))),
|
||||||
|
],
|
||||||
|
onChanged: (v) => c.updateSettings((x) => x.mkvCompactList = v ?? 'auto'),
|
||||||
|
),
|
||||||
|
),
|
||||||
SettingTile(
|
SettingTile(
|
||||||
title: Text(tr('동시 MKV 변환 수')),
|
title: Text(tr('동시 MKV 변환 수')),
|
||||||
subtitle: Text(tr('여러 동영상을 MKV 로 만들 때 동시에 처리할 개수 (재인코딩은 CPU 를 많이 씁니다)')),
|
subtitle: Text(tr('여러 동영상을 MKV 로 만들 때 동시에 처리할 개수 (재인코딩은 CPU 를 많이 씁니다)')),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||||
|
import 'package:jj_mkvmaker/core/models.dart';
|
||||||
|
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||||||
|
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||||||
|
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||||||
|
import 'package:jj_mkvmaker/ui/home_page.dart';
|
||||||
|
|
||||||
|
/// 15 지적 (10/17, 시험 캡처 412 · 960) · 관리자 판단:
|
||||||
|
/// ① 동영상이 있는데 고른 것이 없으면 빈 목록 안내가 아니라 "목록에서 누르세요"
|
||||||
|
/// ② 폭 412 위 버튼 줄이 잘리면 "더 있음" 표시 ([›])
|
||||||
|
/// ③ 폰 세로 목록은 32% 고정이 아니라 내용만큼 (자세히 칸 자리는 남김) · 설정 "폰 세로 목록 크기"
|
||||||
|
void main() {
|
||||||
|
AppController make(int n, {String size = 'auto', bool pick = false}) {
|
||||||
|
final c = AppController(PlatformServices(
|
||||||
|
mediaTool: ProcessMediaTool('x', 'y'),
|
||||||
|
storage: DesktopStorageService(),
|
||||||
|
createRecognizer: () => throw UnimplementedError(),
|
||||||
|
createTranslator: () => throw UnimplementedError(),
|
||||||
|
));
|
||||||
|
c.settings
|
||||||
|
..showLog = true
|
||||||
|
..mkvCompactList = size;
|
||||||
|
for (var i = 0; i < n; i++) {
|
||||||
|
c.videos.add(VideoItem('D:/v/v$i.mp4'));
|
||||||
|
}
|
||||||
|
if (pick) c.selected = c.videos.first;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> show(WidgetTester tester, AppController c, Size size) async {
|
||||||
|
tester.view.physicalSize = size;
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
await tester.pumpWidget(MaterialApp(home: HomePage(c: c)));
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 목록 (v0.mp4 가 있는 스크롤) 의 높이
|
||||||
|
double listHeight(WidgetTester tester) =>
|
||||||
|
tester.getRect(find.ancestor(of: find.text('v0.mp4'), matching: find.byType(Scrollable)).first).height;
|
||||||
|
|
||||||
|
for (final size in const [Size(412, 869), Size(1280, 800)]) {
|
||||||
|
testWidgets('① ${size.width.toInt()}: 동영상이 있고 고른 것이 없으면 "목록에서 누르세요" (빈 목록 안내 아님)', (tester) async {
|
||||||
|
await show(tester, make(50), size);
|
||||||
|
expect(find.text('고른 동영상이 없습니다'), findsOneWidget);
|
||||||
|
expect(find.text('"동영상 추가" 로 파일을 선택하세요'), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('빈 목록이면 예전처럼 [동영상 추가] 안내', (tester) async {
|
||||||
|
await show(tester, make(0), const Size(412, 869));
|
||||||
|
expect(find.text('"동영상 추가" 로 파일을 선택하세요'), findsOneWidget);
|
||||||
|
expect(find.text('고른 동영상이 없습니다'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('③ 자동 · 50개 · 고른 것 없음: 목록이 화면 대부분 (예전 32% 는 4~5줄)', (tester) async {
|
||||||
|
await show(tester, make(50), const Size(412, 869));
|
||||||
|
// 예전: 목록 칸이 화면 높이의 32% (869 → 278, 머리 줄을 빼면 스크롤 약 234).
|
||||||
|
// 이제는 위 막대 · 인코딩 줄 · 작업 기록을 뺀 칸에서 안내 자리 (120) 만 남긴다
|
||||||
|
expect(listHeight(tester), greaterThan(869 * 0.32 - 44 + 60));
|
||||||
|
expect(find.text('v4.mp4'), findsOneWidget);
|
||||||
|
expect(find.text('고른 동영상이 없습니다'), findsOneWidget, reason: '안내 자리는 남는다');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('③ 자동 · 3개: 목록은 줄 수만큼 - 안내가 목록 바로 아래 칸에 (빈 칸 없음)', (tester) async {
|
||||||
|
await show(tester, make(3), const Size(412, 869));
|
||||||
|
final list = listHeight(tester);
|
||||||
|
final rows = tester.getRect(find.text('v2.mp4')).bottom - tester.getRect(find.text('v0.mp4')).top;
|
||||||
|
expect(list, lessThan(rows + 120), reason: '목록 $list · 줄 $rows - 남는 칸을 목록이 차지하지 않는다');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('③ 설정: 작게 < 크게 < 자동 (50개 · 고른 것 없음)', (tester) async {
|
||||||
|
final h = <String, double>{};
|
||||||
|
for (final s in const ['small', 'large', 'auto']) {
|
||||||
|
await show(tester, make(50, size: s), const Size(412, 869));
|
||||||
|
h[s] = listHeight(tester);
|
||||||
|
}
|
||||||
|
expect(h['small']!, lessThan(h['large']!));
|
||||||
|
expect(h['large']!, lessThan(h['auto']!));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('③ 자동 · 고른 동영상: 자세히 칸이 화면 높이의 절반 넘게 (261 · 266 의 버튼이 밀리지 않게)', (tester) async {
|
||||||
|
await show(tester, make(50, pick: true), const Size(412, 869));
|
||||||
|
expect(listHeight(tester), lessThan(869 * 0.4));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('② 폭 412: 위 버튼 줄이 넘치면 [›] - 누르면 밀리고 [‹] 가 생긴다', (tester) async {
|
||||||
|
await show(tester, make(5, pick: true), const Size(412, 869));
|
||||||
|
expect(find.byTooltip('버튼 더 보기'), findsOneWidget);
|
||||||
|
expect(find.byTooltip('앞의 버튼 보기'), findsNothing);
|
||||||
|
await tester.tap(find.byTooltip('버튼 더 보기'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byTooltip('앞의 버튼 보기'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('작업 기록이 비었으면 무엇이 들어오는 칸인지 적는다', (tester) async {
|
||||||
|
final c = make(3);
|
||||||
|
c.logs.clear();
|
||||||
|
await show(tester, c, const Size(412, 869));
|
||||||
|
expect(find.textContaining('작업 기록이 아직 없습니다'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue