WIP ⑦ 밝은 화면 (나) 2: 보기 · 재생 · 나머지 화면 · 캡처 시험 넓힘 (검토 대기)
관리자 지시 10/21 (⑦ (나) 둘째 이후):
- 보기 화면 · 재생 막대는 밝은 화면에서도 어두운 띠 (그림 · 동영상 위 - 사진 보기 앱처럼, 내 판단).
그 띠 위 강조 (켜진 단추 · 쪽 슬라이더 · 썸네일 테두리 · 재생 위치) 는 어두운 화면의 청록, 검은 바탕의 흐린 글자도 어두운 값
- 시간줄: 재생 위치 선 · 고른 칸 글자 (밝은 화면), AI 보라 (aiColor) 는 밝은 화면에서 진하게,
다운로드 진행 막대 글자는 밝은 화면에서 진한 글자 + 흰 테두리
- 캡처 시험: 보기 화면 캡처 (windows_reader_shot_test) 를 새로 · 662 · i18n (앱 전체는 설정 화면 밝기) · check · magnet 에도 JJ_SHOT_THEME
- 시험 흔들림: 보관함 2단계 시험 정리 때 폴더를 잠깐 잡고 있으면 (errno 32) 몇 번 더 지움
- 확인: 어두운 화면 캡처를 808300b 와 픽셀로 견줘 (같은 커밋 두 번 찍은 흔들림 밖) 차이 0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xplore_p3b
parent
808300b69e
commit
0a9a2d0af5
|
|
@ -21,6 +21,8 @@ import 'package:path/path.dart' as p;
|
|||
/// 설정은 메모리에만 (저장하지 않음), 즐겨찾기는 시험 폴더 (JJ_SHOT_TMP).
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_662');
|
||||
final key = GlobalKey();
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import 'support/temp_dir.dart';
|
|||
/// 시험 폴더 · 메모리 저장소만 쓴다 (사용자 설정 · 비밀번호는 건드리지 않음). 휴지통에 넣은 시험 항목은 끝에 $I 로 골라 지운다.
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots');
|
||||
// 시험 폴더: 항목이 아주 많은 시스템 임시 폴더 대신 (JJ_SHOT_TMP, 없으면 시스템 임시 폴더)
|
||||
Directory tempDir(String prefix) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart' show kSecondaryButton;
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:jj_mkvmaker/ui/theme.dart';
|
||||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||
import 'package:jj_mkvmaker/app/i18n_controller.dart';
|
||||
import 'package:jj_mkvmaker/core/models.dart';
|
||||
|
|
@ -20,6 +21,8 @@ import 'package:path/path.dart' as p;
|
|||
/// 라이선스 화면 · 글 칸의 복사 · 붙여넣기 메뉴 · 뒤로 버튼 이름. 설정은 메모리에만 (저장하지 않음).
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_i18n');
|
||||
final navKey = GlobalKey<NavigatorState>();
|
||||
final shotKey = GlobalKey();
|
||||
|
|
@ -47,6 +50,7 @@ void main() {
|
|||
t.view.physicalSize = const Size(1280, 800);
|
||||
t.view.devicePixelRatio = 1;
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.uiBrightness = 'light';
|
||||
await t.runAsync(() => i18n.apply(lang, save: false));
|
||||
await t.runAsync(() => t.pumpWidget(RepaintBoundary(key: shotKey, child: JjMkvMakerApp(controller: c, navigatorKey: navKey))));
|
||||
await settle(t);
|
||||
|
|
@ -88,6 +92,7 @@ void main() {
|
|||
t.view.devicePixelRatio = 1;
|
||||
addTearDown(t.view.reset);
|
||||
final c = AppController(PlatformServices.create());
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') c.settings.uiBrightness = 'light';
|
||||
for (var i = 0; i < checked; i++) {
|
||||
final v = VideoItem(p.join(Directory.systemTemp.path, 'jj_i18n_none', 'v$i.mp4'));
|
||||
c.videos.add(v);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class _Quiet implements DownloadBackend {
|
|||
/// 실행: flutter test integration_test\windows_magnet_shot_test.dart -d windows (JJ_SHOT_DIR 에 PNG)
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
// ⑦ 밝은 화면 캡처: JJ_SHOT_THEME=light (JJ_SHOT_DIR 를 따로 두어 어두운 것과 견줌)
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_magnet');
|
||||
const h = 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef0123456';
|
||||
for (final lang in ['ko', 'en', 'ja']) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||
import 'package:jj_mkvmaker/core/reader_sources.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/reader_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/theme.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
/// ⑦ 밝은 화면 (관리자 지시 10/21): 보기 화면 (그림 · 위아래 막대 · 썸네일 줄) 을 PNG 로 (JJ_SHOT_DIR, JJ_SHOT_THEME=light).
|
||||
/// 시험 그림은 임시 폴더에만 만들고 끝나면 지운다
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
if (Platform.environment['JJ_SHOT_THEME'] == 'light') JjColors.use(Brightness.light);
|
||||
|
||||
testWidgets('보기 화면 그림', (t) async {
|
||||
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_reader');
|
||||
Directory(out).createSync(recursive: true);
|
||||
final tmp = Directory.systemTemp.createTempSync('jj_readershot_');
|
||||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||||
final paths = <String>[];
|
||||
for (var i = 0; i < 4; i++) {
|
||||
final im = img.Image(width: 600, height: 400);
|
||||
img.fill(im, color: img.ColorRgb8(40 + i * 50, 120, 200 - i * 40));
|
||||
img.drawString(im, 'page ${i + 1}', font: img.arial48, x: 200, y: 170);
|
||||
final f = File(p.join(tmp.path, 'p${i + 1}.png'))..writeAsBytesSync(img.encodePng(im));
|
||||
paths.add(f.path);
|
||||
}
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
final key = GlobalKey();
|
||||
for (final (name, size) in [('960', const Size(960, 600)), ('412', const Size(412, 800))]) {
|
||||
t.view.physicalSize = size;
|
||||
t.view.devicePixelRatio = 1;
|
||||
await t.runAsync(() => t.pumpWidget(RepaintBoundary(
|
||||
key: key,
|
||||
child: MaterialApp(
|
||||
theme: buildTheme(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: ReaderPage(c: c, source: ImageFilesSource(paths, tempDir: tmp.path, title: 'shot'), start: 1),
|
||||
),
|
||||
)));
|
||||
for (var i = 0; i < 20; i++) {
|
||||
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 100)));
|
||||
await t.pump(const Duration(milliseconds: 50));
|
||||
}
|
||||
final boundary = key.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||||
final image = await t.runAsync(() => boundary.toImage());
|
||||
final bytes = await t.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png));
|
||||
File(p.join(out, 'reader_$name.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
|
||||
await t.pumpWidget(const SizedBox());
|
||||
}
|
||||
t.view.reset();
|
||||
});
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ String aiStartNotice(List<VideoItem> videos, AiOptions o, {required bool thenBui
|
|||
}
|
||||
|
||||
/// 138: AI 작업 (자막 · 자막 & MKV) 의 색 - [MKV 만들기] (청록) 와 헷갈리지 않게
|
||||
const aiColor = Color(0xFFB388FF);
|
||||
Color get aiColor => JjColors.isLight ? const Color(0xFF6A3FC8) : const Color(0xFFB388FF); // ⑦ 밝은 화면은 진한 보라 (대비 4.5:1)
|
||||
|
||||
/// 138: AI 작업 시간 어림 (분): 영상 1시간 ≈ 음성인식 15~30분 + 번역할 언어마다 10~20분. 길이를 모르면 null
|
||||
(int, int)? aiEstimateMinutes(Duration total, int translateLanguages) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ class AudioNowPlaying extends StatelessWidget {
|
|||
valueListenable: state,
|
||||
builder: (_, s, _) => Text(
|
||||
'${_t(s.position)} / ${_t(s.duration)}',
|
||||
style: TextStyle(color: JjColors.textDim, fontSize: 14, fontFeatures: [FontFeature.tabularFigures()]),
|
||||
// 검은 바탕 위 - 밝은 화면에서도 어두운 화면의 흐린 글자색
|
||||
style: TextStyle(color: JjPalette.dark.textDim, fontSize: 14, fontFeatures: [FontFeature.tabularFigures()]),
|
||||
),
|
||||
),
|
||||
]),
|
||||
|
|
@ -65,7 +66,7 @@ class _Note extends StatelessWidget {
|
|||
aspectRatio: 1,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: Color(0xFF1E1E1E), borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
child: Center(child: Icon(Icons.music_note, size: 96, color: JjColors.textDim)),
|
||||
child: Center(child: Icon(Icons.music_note, size: 96, color: JjPalette.dark.textDim)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -443,8 +443,9 @@ class DownloadProgressBar extends StatelessWidget {
|
|||
fontSize: height * 0.62,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
shadows: const [Shadow(blurRadius: 2, color: Colors.black)],
|
||||
// ⑦ 밝은 화면: 옅은 막대 위에서도 읽히게 진한 글자 + 흰 테두리
|
||||
color: JjColors.isLight ? JjColors.text : Colors.white,
|
||||
shadows: JjColors.isLight ? const [Shadow(blurRadius: 3, color: Colors.white)] : const [Shadow(blurRadius: 2, color: Colors.black)],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -439,7 +439,8 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
icon: Icon(i),
|
||||
);
|
||||
return Container(
|
||||
color: (_full || _popup) ? Colors.black.withValues(alpha: 0.6) : JjColors.panel,
|
||||
// ⑦ 재생 막대는 밝은 화면에서도 어두운 띠 (흰 단추 · 시각 글자가 보이게)
|
||||
color: (_full || _popup) ? Colors.black.withValues(alpha: 0.6) : JjPalette.dark.panel,
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 4),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Row(children: [
|
||||
|
|
@ -447,7 +448,14 @@ class _PlayerPageState extends State<PlayerPage> {
|
|||
style: const TextStyle(fontSize: 12, color: Colors.white70, fontFamily: 'Consolas')),
|
||||
Expanded(
|
||||
child: SliderTheme(
|
||||
data: const SliderThemeData(trackHeight: 3, overlayShape: RoundSliderOverlayShape(overlayRadius: 10)),
|
||||
data: JjColors.isLight
|
||||
? SliderThemeData(
|
||||
trackHeight: 3,
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 10),
|
||||
activeTrackColor: JjPalette.dark.accent,
|
||||
thumbColor: JjPalette.dark.accent,
|
||||
inactiveTrackColor: Colors.white24)
|
||||
: const SliderThemeData(trackHeight: 3, overlayShape: RoundSliderOverlayShape(overlayRadius: 10)),
|
||||
child: Slider(
|
||||
value: pos,
|
||||
max: total <= 0 ? 1 : total,
|
||||
|
|
|
|||
|
|
@ -804,7 +804,8 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
|
||||
Widget _barButton(_Act a, {bool compact = false}) => IconButton(
|
||||
tooltip: a.tip,
|
||||
color: a.on ? JjColors.accent : Colors.white,
|
||||
// 막대는 밝은 화면에서도 어두운 띠 - 강조는 어두운 화면의 청록 (밝은 화면의 진한 청록은 어두운 띠 위에서 안 보임)
|
||||
color: a.on ? JjPalette.dark.accent : Colors.white,
|
||||
disabledColor: Colors.white38,
|
||||
icon: a.icon,
|
||||
visualDensity: compact ? VisualDensity.compact : null,
|
||||
|
|
@ -1005,7 +1006,7 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
child: Container(
|
||||
margin: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: i == _cur.page ? JjColors.accent : Colors.white24, width: i == _cur.page ? 2 : 1)),
|
||||
border: Border.all(color: i == _cur.page ? JjPalette.dark.accent : Colors.white24, width: i == _cur.page ? 2 : 1)),
|
||||
child: FutureBuilder<ReaderImage>(
|
||||
future: _thumb(i),
|
||||
builder: (_, snap) {
|
||||
|
|
@ -1048,6 +1049,8 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
max: (count - 1).toDouble(),
|
||||
divisions: count - 1,
|
||||
onChanged: (v) => _pages.jumpToPage(v.round()),
|
||||
// ⑦ 어두운 막대 위 - 밝은 화면에서도 어두운 화면의 청록 (null 이면 테마 그대로)
|
||||
activeColor: JjColors.isLight ? JjPalette.dark.accent : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -151,7 +151,8 @@ class _SubtitleTimelineState extends State<SubtitleTimeline> {
|
|||
top: 0,
|
||||
bottom: 0,
|
||||
child: IgnorePointer(
|
||||
child: Container(width: 2, color: Colors.white),
|
||||
// ⑦ 밝은 화면: 흰 선은 밝은 바탕에서 안 보여 글자색으로
|
||||
child: Container(width: 2, color: JjColors.isLight ? JjColors.text : Colors.white),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -254,7 +255,7 @@ class _SubtitleTimelineState extends State<SubtitleTimeline> {
|
|||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
height: 1.2,
|
||||
color: sel ? Colors.black : JjColors.text),
|
||||
color: sel ? (JjColors.isLight ? JjColors.onAccent : Colors.black) : JjColors.text),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -29,11 +29,20 @@ void main() {
|
|||
Directory(p.join(tmp.path, 'out')).createSync();
|
||||
vaultIterationsForTest = 1000;
|
||||
});
|
||||
tearDown(() {
|
||||
tearDown(() async {
|
||||
vaultDirOf = was;
|
||||
vaultPickFolder = wasPick;
|
||||
MasterLock.instance = null;
|
||||
tmp.deleteSync(recursive: true);
|
||||
// Windows 는 막 쓴 파일을 백신 · 색인이 잠깐 잡고 있을 수 있다 (errno 32) - 몇 번 더
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
tmp.deleteSync(recursive: true);
|
||||
break;
|
||||
} on FileSystemException {
|
||||
if (i >= 20) rethrow;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 250));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AppController ctl() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue