WIP 261 폰 세로 MKV 세부 정보: 자막 버튼을 칩 바로 아래로 - 감지한 언어 줄 · 이동 버튼이 [AI 자막 만들기] 를 밀어내지 않게 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>backup_묶음6_d46e089
parent
5646c976a8
commit
2d2bd40824
|
|
@ -1075,7 +1075,53 @@ class _VideoDetail extends StatelessWidget {
|
||||||
|
|
||||||
Widget _list(BuildContext context, MediaInfo? info, StreamInfo? video, int audioCount, bool locked,
|
Widget _list(BuildContext context, MediaInfo? info, StreamInfo? video, int audioCount, bool locked,
|
||||||
{Widget? moveButtons, double bottom = 88}) {
|
{Widget? moveButtons, double bottom = 88}) {
|
||||||
final side = isCompact(context) ? 12.0 : 20.0;
|
final compact = isCompact(context);
|
||||||
|
final side = compact ? 12.0 : 20.0;
|
||||||
|
// 198: 마지막 AI 자막의 원어를 자동 감지했으면 - 알림을 놓쳐도 여기서 다른 언어로 다시 만들 수 있게
|
||||||
|
final detected = v.aiDetected == null
|
||||||
|
? null
|
||||||
|
: Wrap(crossAxisAlignment: WrapCrossAlignment.center, spacing: 8, children: [
|
||||||
|
Text(trf('감지한 언어: {0} (자동 감지)', [languageOf(v.aiDetected).name]),
|
||||||
|
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: c.busy
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final lang = await pickSourceLanguage(context, languageOf(v.aiDetected));
|
||||||
|
if (lang != null) await c.generateAiSubtitles([v], c.aiOptions.copyWith(source: lang));
|
||||||
|
},
|
||||||
|
child: Text(tr('다른 언어로 다시')),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
// 자막 머리 줄: 좁으면 버튼이 다음 줄로 (오른쪽 정렬)
|
||||||
|
final subtitleActions = Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8),
|
||||||
|
child: Text(tr('자막'), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Wrap(alignment: WrapAlignment.end, spacing: 8, runSpacing: 8, children: [
|
||||||
|
if (c.aiAvailable)
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: locked || c.ffmpegVersion == null ? null : () => showAiDialog(context, c, v),
|
||||||
|
icon: const Icon(Icons.auto_awesome, size: 18),
|
||||||
|
label: Text(tr('AI 자막 만들기')),
|
||||||
|
),
|
||||||
|
if (c.subtitleProvider != null)
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: locked ? null : () => showSubtitleSearch(context, c, v),
|
||||||
|
icon: const Icon(Icons.travel_explore, size: 18),
|
||||||
|
label: Text(tr('인터넷 자막 찾기')),
|
||||||
|
),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: locked ? null : () => c.pickSubtitlesFor(v),
|
||||||
|
icon: const Icon(Icons.subtitles_outlined, size: 18),
|
||||||
|
label: Text(tr('자막 파일 추가')),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]);
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: EdgeInsets.fromLTRB(side, side, side, bottom),
|
padding: EdgeInsets.fromLTRB(side, side, side, bottom),
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -1111,26 +1157,16 @@ class _VideoDetail extends StatelessWidget {
|
||||||
],
|
],
|
||||||
_Chip(trf('음성 {0}개', [audioCount])),
|
_Chip(trf('음성 {0}개', [audioCount])),
|
||||||
]),
|
]),
|
||||||
if (moveButtons != null) ...[
|
// 261: 좁은 화면 (폰 세로) 은 세부 정보 칸이 낮다 (노트 10+ 에서 약 210) - "감지한 언어" 줄 · 이동 버튼이
|
||||||
const SizedBox(height: 10),
|
// [AI 자막 만들기] 를 칸 밖으로 밀었다. 자막 버튼을 칩 바로 아래에 두고, 감지한 언어 · 이동 버튼은 그 다음
|
||||||
moveButtons,
|
if (compact) ...[
|
||||||
],
|
const SizedBox(height: 12),
|
||||||
// 198: 마지막 AI 자막의 원어를 자동 감지했으면 - 알림을 놓쳐도 여기서 다른 언어로 다시 만들 수 있게
|
subtitleActions,
|
||||||
if (v.aiDetected != null) ...[
|
if (detected != null) ...[const SizedBox(height: 4), detected],
|
||||||
const SizedBox(height: 8),
|
if (moveButtons != null) ...[const SizedBox(height: 10), moveButtons],
|
||||||
Wrap(crossAxisAlignment: WrapCrossAlignment.center, spacing: 8, children: [
|
] else ...[
|
||||||
Text(trf('감지한 언어: {0} (자동 감지)', [languageOf(v.aiDetected).name]),
|
if (moveButtons != null) ...[const SizedBox(height: 10), moveButtons],
|
||||||
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
if (detected != null) ...[const SizedBox(height: 8), detected],
|
||||||
TextButton(
|
|
||||||
onPressed: c.busy
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
final lang = await pickSourceLanguage(context, languageOf(v.aiDetected));
|
|
||||||
if (lang != null) await c.generateAiSubtitles([v], c.aiOptions.copyWith(source: lang));
|
|
||||||
},
|
|
||||||
child: Text(tr('다른 언어로 다시')),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
if (v.message != null) ...[
|
if (v.message != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
@ -1144,36 +1180,10 @@ class _VideoDetail extends StatelessWidget {
|
||||||
style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 24),
|
if (!compact) ...[
|
||||||
// 좁으면 버튼이 다음 줄로 (오른쪽 정렬)
|
const SizedBox(height: 24),
|
||||||
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
subtitleActions,
|
||||||
Padding(
|
],
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Text(tr('자막'), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Wrap(alignment: WrapAlignment.end, spacing: 8, runSpacing: 8, children: [
|
|
||||||
if (c.aiAvailable)
|
|
||||||
FilledButton.icon(
|
|
||||||
onPressed: locked || c.ffmpegVersion == null ? null : () => showAiDialog(context, c, v),
|
|
||||||
icon: const Icon(Icons.auto_awesome, size: 18),
|
|
||||||
label: Text(tr('AI 자막 만들기')),
|
|
||||||
),
|
|
||||||
if (c.subtitleProvider != null)
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: locked ? null : () => showSubtitleSearch(context, c, v),
|
|
||||||
icon: const Icon(Icons.travel_explore, size: 18),
|
|
||||||
label: Text(tr('인터넷 자막 찾기')),
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: locked ? null : () => c.pickSubtitlesFor(v),
|
|
||||||
icon: const Icon(Icons.subtitles_outlined, size: 18),
|
|
||||||
label: Text(tr('자막 파일 추가')),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (v.subtitles.isEmpty)
|
if (v.subtitles.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||||
|
import 'package:jj_mkvmaker/app/settings.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';
|
||||||
|
|
||||||
|
/// 261: 폰 세로 (노트 10+ 412×869) 의 MKV 세부 정보 - "감지한 언어" 줄이 생겨도 [AI 자막 만들기] 가 밀려 가려지지 않는다
|
||||||
|
void main() {
|
||||||
|
for (final detected in [false, true]) {
|
||||||
|
testWidgets('261: 폰 세로 · 작업 기록 켬 · 감지한 언어 ${detected ? '있음' : '없음'} - [AI 자막 만들기] 가 스크롤 없이 보인다', (tester) async {
|
||||||
|
tester.view.physicalSize = const Size(412, 869);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
final c = AppController(PlatformServices(
|
||||||
|
mediaTool: ProcessMediaTool('x', 'y'),
|
||||||
|
storage: DesktopStorageService(),
|
||||||
|
createRecognizer: () => throw UnimplementedError(),
|
||||||
|
createTranslator: () => throw UnimplementedError(),
|
||||||
|
));
|
||||||
|
c.settings.showLog = true;
|
||||||
|
c.settings.moveTargets = [MoveTarget('보관', r'D:\보관')];
|
||||||
|
for (var i = 1; i <= 4; i++) {
|
||||||
|
c.videos.add(VideoItem('D:/v/아주 긴 이름의 동영상 파일 $i.mp4'));
|
||||||
|
}
|
||||||
|
c.selected = c.videos.first..aiDetected = detected ? 'en' : null;
|
||||||
|
await tester.pumpWidget(MaterialApp(home: HomePage(c: c)));
|
||||||
|
await tester.pump();
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
final ai = find.text('AI 자막 만들기');
|
||||||
|
// 화면 밖이면 ListView 가 아예 만들지 않는다
|
||||||
|
expect(ai, findsOneWidget, reason: '세부 정보 칸 아래로 밀림');
|
||||||
|
final view = tester.getRect(find.ancestor(of: ai, matching: find.byType(Scrollable)).first);
|
||||||
|
final button = tester.getRect(ai);
|
||||||
|
expect(button.bottom, lessThanOrEqualTo(view.bottom), reason: '버튼 $button · 세부 정보 칸 $view');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue