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? 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(
|
||||
padding: EdgeInsets.fromLTRB(side, side, side, bottom),
|
||||
children: [
|
||||
|
|
@ -1111,26 +1157,16 @@ class _VideoDetail extends StatelessWidget {
|
|||
],
|
||||
_Chip(trf('음성 {0}개', [audioCount])),
|
||||
]),
|
||||
if (moveButtons != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
moveButtons,
|
||||
],
|
||||
// 198: 마지막 AI 자막의 원어를 자동 감지했으면 - 알림을 놓쳐도 여기서 다른 언어로 다시 만들 수 있게
|
||||
if (v.aiDetected != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
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('다른 언어로 다시')),
|
||||
),
|
||||
]),
|
||||
// 261: 좁은 화면 (폰 세로) 은 세부 정보 칸이 낮다 (노트 10+ 에서 약 210) - "감지한 언어" 줄 · 이동 버튼이
|
||||
// [AI 자막 만들기] 를 칸 밖으로 밀었다. 자막 버튼을 칩 바로 아래에 두고, 감지한 언어 · 이동 버튼은 그 다음
|
||||
if (compact) ...[
|
||||
const SizedBox(height: 12),
|
||||
subtitleActions,
|
||||
if (detected != null) ...[const SizedBox(height: 4), detected],
|
||||
if (moveButtons != null) ...[const SizedBox(height: 10), moveButtons],
|
||||
] else ...[
|
||||
if (moveButtons != null) ...[const SizedBox(height: 10), moveButtons],
|
||||
if (detected != null) ...[const SizedBox(height: 8), detected],
|
||||
],
|
||||
if (v.message != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
|
|
@ -1144,36 +1180,10 @@ class _VideoDetail extends StatelessWidget {
|
|||
style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
// 좁으면 버튼이 다음 줄로 (오른쪽 정렬)
|
||||
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('자막 파일 추가')),
|
||||
),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
if (!compact) ...[
|
||||
const SizedBox(height: 24),
|
||||
subtitleActions,
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
if (v.subtitles.isEmpty)
|
||||
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