WIP X-plore 맞추기 (관리자 지시 10/20): 보기 화면 볼륨 키로 넘기기 (아래 = 다음 장, 위 = 이전 장, Android) - 처음 끔 (환경 설정 > 보기), 켰을 때만 보기 화면에서 가로채고 (처리한 키는 시스템 볼륨을 바꾸지 않음) 동영상 · 다른 화면은 그대로 · 시험 (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>xplore_view
parent
99de4f4bc9
commit
ed24463192
|
|
@ -1411,6 +1411,7 @@
|
|||
"보기": "View",
|
||||
"보기 {0}초 · Ctrl+휠 확대/축소": "View {0} s · Ctrl+wheel to zoom",
|
||||
"보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "Shows [Improve quality] in the viewer: refines with AI image (image → image, light) and then upscales. Saves a new file (name_q); needs the AI image model",
|
||||
"보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "In the viewer, volume down = next, volume up = previous (videos and other screens keep normal volume keys)",
|
||||
"보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "How often [Slideshow] in the viewer turns to the next page",
|
||||
"보내지 못했습니다": "Could not send",
|
||||
"보낼 것 {0} / 있는 것 {1}": "sending {0} / existing {1}",
|
||||
|
|
@ -1461,6 +1462,7 @@
|
|||
"복사했습니다.": "copied.",
|
||||
"볼 그림이 없습니다.": "No images to show.",
|
||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "This video is unavailable (deleted · region restricted).",
|
||||
"볼륨 키로 넘기기": "Turn pages with volume keys",
|
||||
"분석": "Analyze",
|
||||
"분석 실패: {0} - {1}": "Analysis failed: {0} - {1}",
|
||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "Re-analyzed {0} videos that had failed",
|
||||
|
|
|
|||
|
|
@ -1411,6 +1411,7 @@
|
|||
"보기": "表示",
|
||||
"보기 {0}초 · Ctrl+휠 확대/축소": "表示 {0}秒・Ctrl+ホイールで拡大/縮小",
|
||||
"보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "ビューアーに [画質を上げる]: AI 画像 (画像 → 画像、弱め) で整えてから解像度を上げます。新しいファイル (名前_q) に保存し、AI 画像モデルが必要です",
|
||||
"보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "ビューアーで音量下 = 次、音量上 = 前 (動画・ほかの画面の音量はそのまま)",
|
||||
"보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "表示画面の [連続表示] が次のページへ進む間隔",
|
||||
"보내지 못했습니다": "送信できませんでした",
|
||||
"보낼 것 {0} / 있는 것 {1}": "送る方 {0} / 既存 {1}",
|
||||
|
|
@ -1461,6 +1462,7 @@
|
|||
"복사했습니다.": "コピーしました。",
|
||||
"볼 그림이 없습니다.": "表示する画像がありません。",
|
||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "視聴できない動画です(削除・地域制限)。",
|
||||
"볼륨 키로 넘기기": "音量キーでページ送り",
|
||||
"분석": "分析",
|
||||
"분석 실패: {0} - {1}": "分析失敗: {0} - {1}",
|
||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "分析に失敗していた動画 {0} 件を再分析しました",
|
||||
|
|
|
|||
|
|
@ -1411,6 +1411,7 @@
|
|||
"보기": "查看",
|
||||
"보기 {0}초 · Ctrl+휠 확대/축소": "显示 {0} 秒 · Ctrl+滚轮缩放",
|
||||
"보기 화면에 [품질 올리기]: AI 그림 (그림 → 그림, 약하게) 으로 다듬은 뒤 해상도 올리기를 이어서. 새 파일 (이름_q) 로 저장하며 AI 그림 모델이 필요합니다": "在查看器中显示 [提升画质]: 先用 AI 图片 (图生图,轻度) 优化再提高分辨率。保存为新文件 (名称_q),需要 AI 图片模型",
|
||||
"보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)": "在查看器中音量减 = 下一张, 音量加 = 上一张 (视频和其他界面的音量不变)",
|
||||
"보기 화면의 [계속 보기] 가 다음 장으로 넘기는 간격": "浏览画面中 [连续浏览] 翻到下一页的间隔",
|
||||
"보내지 못했습니다": "无法发送",
|
||||
"보낼 것 {0} / 있는 것 {1}": "要发送 {0} / 已有 {1}",
|
||||
|
|
@ -1461,6 +1462,7 @@
|
|||
"복사했습니다.": "已复制。",
|
||||
"볼 그림이 없습니다.": "没有可显示的图片。",
|
||||
"볼 수 없는 영상입니다 (삭제 · 지역 제한).": "该视频无法观看(已删除 · 地区限制)。",
|
||||
"볼륨 키로 넘기기": "用音量键翻页",
|
||||
"분석": "分析",
|
||||
"분석 실패: {0} - {1}": "分析失败: {0} - {1}",
|
||||
"분석에 실패했던 동영상 {0}개를 다시 분석했습니다": "已重新分析之前分析失败的 {0} 个视频",
|
||||
|
|
|
|||
|
|
@ -558,6 +558,10 @@ class AppSettings {
|
|||
/// X-plore 맞추기 (관리자 지시 10/20): 그림 보기 화면 아래 갤러리 줄 (썸네일, 누르면 그 장으로). 처음 끔
|
||||
bool readerGallery = false;
|
||||
|
||||
/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면에서 볼륨 키로 넘기기 (아래 = 다음 장, 위 = 이전 장, Android). 처음 끔.
|
||||
/// 보기 화면에서만 가로챈다 (동영상 · 다른 화면의 볼륨은 그대로)
|
||||
bool readerVolumeKeys = false;
|
||||
|
||||
/// 119: 한 장에 두 쪽이 붙은 그림을 반씩 나눠 보기 - 'auto' 가로가 세로보다 긴 그림만 · 'on' 늘 · 'off' 안 함
|
||||
String readerSplit = 'auto';
|
||||
|
||||
|
|
@ -871,6 +875,7 @@ class AppSettings {
|
|||
'readerSystemBars': readerSystemBars,
|
||||
'readerAutoSeconds': readerAutoSeconds,
|
||||
'readerGallery': readerGallery,
|
||||
'readerVolumeKeys': readerVolumeKeys,
|
||||
'readerSplit': readerSplit,
|
||||
'startMenuShortcut': startMenuShortcut,
|
||||
'syncStopToast': syncStopToast,
|
||||
|
|
@ -1127,6 +1132,7 @@ class AppSettings {
|
|||
..readerSystemBars = j['readerSystemBars'] != false
|
||||
..readerAutoSeconds = ((j['readerAutoSeconds'] as num?)?.toInt() ?? 3).clamp(1, 60)
|
||||
..readerGallery = j['readerGallery'] as bool? ?? false
|
||||
..readerVolumeKeys = j['readerVolumeKeys'] as bool? ?? false
|
||||
..readerSplit = const ['auto', 'on', 'off'].contains(j['readerSplit']) ? j['readerSplit'] as String : 'auto'
|
||||
..startMenuShortcut = j['startMenuShortcut'] != false
|
||||
..syncStopToast = j['syncStopToast'] != false
|
||||
|
|
|
|||
|
|
@ -72,6 +72,15 @@ class ViewerSettings extends StatelessWidget {
|
|||
title: Text(tr('그림 보기 아래 갤러리 줄')),
|
||||
subtitle: Text(tr('그림을 볼 때 아래에 작은 그림 줄을 보입니다 (누르면 그 장으로)')),
|
||||
),
|
||||
// X-plore 맞추기 (관리자 지시 10/20): 볼륨 키로 넘기기 (Android)
|
||||
if (Platform.isAndroid)
|
||||
SwitchListTile(
|
||||
secondary: const Icon(Icons.volume_down_outlined),
|
||||
value: s.readerVolumeKeys,
|
||||
onChanged: (v) => c.updateSettings((x) => x.readerVolumeKeys = v),
|
||||
title: Text(tr('볼륨 키로 넘기기')),
|
||||
subtitle: Text(tr('보기 화면에서 볼륨 아래 = 다음 장, 위 = 이전 장 (동영상 · 다른 화면의 볼륨은 그대로)')),
|
||||
),
|
||||
// 117: 계속 보기 간격
|
||||
SettingTile(
|
||||
leading: const Icon(Icons.slideshow_outlined),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -50,6 +51,9 @@ class ReaderPage extends StatefulWidget {
|
|||
final bool autoPlay;
|
||||
const ReaderPage({super.key, required this.c, required this.source, this.start = 0, this.autoPlay = false});
|
||||
|
||||
/// 볼륨 키로 넘기기를 쓰는 기기인지 (Android - 시험에서 바꿈)
|
||||
static bool Function() volumeKeysSupported = () => Platform.isAndroid;
|
||||
|
||||
/// 계속 보기 동안 화면 켜 두기 (시험에서 바꿈)
|
||||
static Future<void> Function(bool on) keepScreenOn = (on) => on ? WakelockPlus.enable() : WakelockPlus.disable();
|
||||
|
||||
|
|
@ -412,6 +416,15 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
_jumpLater();
|
||||
}
|
||||
|
||||
/// X-plore 맞추기 (관리자 지시 10/20): 볼륨 키로 넘기기 (설정을 켰고 Android 일 때만 - 처리한 키는 시스템 볼륨을 바꾸지 않는다)
|
||||
KeyEventResult _onKey(FocusNode _, KeyEvent e) {
|
||||
if (!c.settings.readerVolumeKeys || !ReaderPage.volumeKeysSupported()) return KeyEventResult.ignored;
|
||||
final k = e.logicalKey;
|
||||
if (k != LogicalKeyboardKey.audioVolumeDown && k != LogicalKeyboardKey.audioVolumeUp) return KeyEventResult.ignored;
|
||||
if (e is KeyDownEvent || e is KeyRepeatEvent) _go(k == LogicalKeyboardKey.audioVolumeDown ? 1 : -1);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
void _go(int delta) {
|
||||
final to = (_index + delta).clamp(0, math.max(0, _v.length - 1)).toInt();
|
||||
if (to == _index) {
|
||||
|
|
@ -933,7 +946,10 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
child: Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: _onKey,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: LayoutBuilder(builder: (context, box) {
|
||||
// 화면 너비 (기기 픽셀) 에 맞춰 그림을 줄여 읽는다
|
||||
|
|
@ -977,6 +993,7 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
],
|
||||
]);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||
|
|
@ -215,6 +215,41 @@ void main() {
|
|||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
||||
testWidgets('X-plore 맞추기: 볼륨 키로 넘기기 - 처음은 끔 (키를 가로채지 않음), 켜면 아래 = 다음 · 위 = 이전 (Android 만)', (tester) async {
|
||||
tester.view.physicalSize = const Size(900, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final was = ReaderPage.volumeKeysSupported;
|
||||
addTearDown(() => ReaderPage.volumeKeysSupported = was);
|
||||
ReaderPage.volumeKeysSupported = () => true;
|
||||
expect(AppSettings().readerVolumeKeys, isFalse);
|
||||
final files = [
|
||||
for (final (i, n) in ['a.png', 'b.png', 'c.png'].indexed) (File(p.join(tmp.path, n))..writeAsBytesSync(_png(i * 80, 0, 0))).path,
|
||||
];
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource(files, tempDir: tmp.path, title: 'imgs'))));
|
||||
await tester.pump();
|
||||
// 끔: 볼륨 키를 가로채지 않는다 (시스템 볼륨이 바뀜)
|
||||
expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isFalse);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('1 / 3'), findsOneWidget);
|
||||
// 켬
|
||||
c.settings.readerVolumeKeys = true;
|
||||
expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isTrue, reason: '처리함 - 시스템 볼륨은 바뀌지 않음');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('3 / 3'), findsOneWidget);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeUp);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
// Android 가 아니면 쓰지 않음
|
||||
ReaderPage.volumeKeysSupported = () => false;
|
||||
expect(await tester.sendKeyEvent(LogicalKeyboardKey.audioVolumeDown), isFalse);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
});
|
||||
|
||||
testWidgets('그림이 깨졌으면 화면 글 + [자세히], 파일을 고친 뒤 [다시 시도] 하면 열린다 (28 지적: 같은 키 · 같은 그림이라 Image 가 다시 읽지 않았다 - 장의 키를 올리고 그림 캐시도 비움)', (tester) async {
|
||||
tester.view.physicalSize = const Size(900, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
|
|
|
|||
Loading…
Reference in New Issue