WIP 계속 보기 시계는 그 장이 다 보인 뒤부터 (관리자 지시 10/20, 28 검토): WebDAV 처럼 느린 원본에서 받는 동안 시간이 흘러 보지도 못한 장을 넘겼다 - 장마다 한 번 타이머 (그림 첫 장면 · 실패 글이 그려진 뒤 시작, 받는 중에는 재지 않음) · 시험: 느린 원본에서 10초 동안 넘기지 않고 다 받은 뒤 3초에 넘김, 기존 시험은 넘긴 뒤 한 장면을 더 지나게 (FutureBuilder 가 그림을 받아 그리는 장면) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>xplore_view
parent
9ff8343835
commit
4ec505db13
|
|
@ -269,32 +269,57 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
/// 계속 보기를 멈추게 한 누름은 장 넘기기 · 막대 숨기기로 쓰지 않는다
|
||||
bool _swallowTap = false;
|
||||
|
||||
/// 관리자 지시 10/20 (28 검토): 다 보인 장 ('$_version:$_index'). 계속 보기 시계는 그 장이 보인 뒤부터 잰다 -
|
||||
/// WebDAV 처럼 느린 원본에서 받는 동안 시간이 흘러 보지도 못한 장을 넘겼다
|
||||
String? _shownKey;
|
||||
|
||||
/// [i] 장이 화면에 다 그려졌다 (그림 첫 장면 · 실패 글). 지금 장이면 계속 보기 시계를 시작
|
||||
void _markShown(int i) {
|
||||
if (!mounted || i != _index) return;
|
||||
final key = '$_version:$i';
|
||||
if (_shownKey == key) return;
|
||||
_shownKey = key;
|
||||
if (_playing && _timer == null) _startTimer();
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: c.settings.readerAutoSeconds), _tick);
|
||||
}
|
||||
|
||||
void _play(bool on) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
if (on && _v.isNotEmpty) {
|
||||
_timer = Timer.periodic(Duration(seconds: c.settings.readerAutoSeconds), (_) => _tick());
|
||||
}
|
||||
// 지금 장이 이미 보이면 바로 재기 시작, 아직 받는 중이면 보인 뒤에 ([_markShown])
|
||||
if (on && _v.isNotEmpty && _shownKey == '$_version:$_index') _startTimer();
|
||||
if (on == _playing) return;
|
||||
setState(() => _playing = on);
|
||||
unawaited(ReaderPage.keepScreenOn(on).catchError((_) {}));
|
||||
}
|
||||
|
||||
void _tick() {
|
||||
if (!mounted) return;
|
||||
_timer = null;
|
||||
if (!mounted || !_playing) return;
|
||||
if (_index >= _v.length - 1) {
|
||||
_play(false);
|
||||
_snack(tr('마지막 장이라 계속 보기를 멈췄습니다.'));
|
||||
return;
|
||||
}
|
||||
_pages.jumpToPage(_index + 1);
|
||||
// 다음 장이 이미 받아 둔 것이라 다시 그리지 않으면 그 자리에서 알린다
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _rearmIfShown());
|
||||
}
|
||||
|
||||
/// 넘긴 뒤 그 장이 곧바로 보였는데 (캐시) 알림을 놓쳤으면 시계를 다시 건다
|
||||
void _rearmIfShown() {
|
||||
if (mounted && _playing && _timer == null && _shownKey == '$_version:$_index') _startTimer();
|
||||
}
|
||||
|
||||
Future<void> _setSeconds(int d) async {
|
||||
await c.updateSettings((x) => x.readerAutoSeconds = (x.readerAutoSeconds + d).clamp(1, 60));
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
if (_playing) _play(true); // 새 간격으로
|
||||
if (_playing) _play(true); // 새 간격으로 (지금 장이 보이면 처음부터 다시)
|
||||
}
|
||||
|
||||
// ───────── 확대 (30) ─────────
|
||||
|
|
@ -521,13 +546,26 @@ class _ReaderPageState extends State<ReaderPage> {
|
|||
key: ValueKey('$_version:${slot.page}:${slot.part}:${_retry[slot.page] ?? 0}'),
|
||||
future: _sharp[i] ?? (half ? _loadHalf(slot.page) : _load(slot.page)),
|
||||
builder: (context, snap) {
|
||||
if (snap.hasError) return Center(child: failed(snap.error));
|
||||
// 계속 보기: 받는 동안은 시계를 재지 않고, 보인 (또는 실패 글이 보인) 뒤부터
|
||||
void shown() => WidgetsBinding.instance.addPostFrameCallback((_) => _markShown(i));
|
||||
if (snap.hasError) {
|
||||
shown();
|
||||
return Center(child: failed(snap.error));
|
||||
}
|
||||
final r = snap.data;
|
||||
if (r == null) return const Center(child: CircularProgressIndicator());
|
||||
if (r.image != null) shown();
|
||||
Widget picture = r.image != null
|
||||
? RawImage(image: r.image, filterQuality: FilterQuality.medium)
|
||||
: Image(image: r.provider!, gaplessPlayback: true, filterQuality: FilterQuality.medium,
|
||||
errorBuilder: (_, e, _) => failed(e, provider: r.provider));
|
||||
frameBuilder: (_, child, frame, sync) {
|
||||
if (frame != null || sync) shown();
|
||||
return child;
|
||||
},
|
||||
errorBuilder: (_, e, _) {
|
||||
shown();
|
||||
return failed(e, provider: r.provider);
|
||||
});
|
||||
// 120: 회전 · 상하 반전 (보기만, 파일은 그대로)
|
||||
if (_turns != 0) picture = RotatedBox(quarterTurns: _turns, child: picture);
|
||||
if (_flipV) picture = Transform.flip(flipY: true, child: picture);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -28,6 +29,18 @@ class _Sized extends ReaderSource {
|
|||
}
|
||||
}
|
||||
|
||||
/// 관리자 지시 10/20: WebDAV 처럼 느린 원본 - [hold] 의 장은 [release] 할 때까지 받는 중
|
||||
class _Slow extends _Sized {
|
||||
final int hold;
|
||||
final release = Completer<void>();
|
||||
_Slow(super.images, this.hold);
|
||||
@override
|
||||
Future<ReaderImage> load(int i, {required int maxWidth}) async {
|
||||
if (i == hold) await release.future;
|
||||
return super.load(i, maxWidth: maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/// 223: 폴더의 그림들처럼 (자동 나누기는 다른 그림과 견주어 판단)
|
||||
class _FolderSized extends ImageFilesSource {
|
||||
final List<ui.Image> images;
|
||||
|
|
@ -201,15 +214,49 @@ void main() {
|
|||
expect(find.text('1 / 4'), findsOneWidget);
|
||||
await t.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('2 / 4'), findsOneWidget);
|
||||
await t.pump(const Duration(seconds: 2));
|
||||
// 관리자 지시 10/20: 시계는 그 장이 다 보인 뒤부터 - 넘긴 뒤 한 장면 (FutureBuilder 가 그림을 받아 그림) 이 지나야 잰다
|
||||
await t.pump();
|
||||
await t.pump(const Duration(milliseconds: 1900));
|
||||
expect(find.text('2 / 4'), findsOneWidget);
|
||||
await t.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('3 / 4'), findsOneWidget);
|
||||
await t.pump();
|
||||
await t.pump(const Duration(seconds: 2));
|
||||
expect(find.text('4 / 4'), findsOneWidget);
|
||||
await t.pump();
|
||||
await t.pump(const Duration(seconds: 2));
|
||||
expect(find.byTooltip('계속 보기 (2초마다 다음 장)'), findsOneWidget, reason: '마지막 장에서 멈춤');
|
||||
expect(screenOn, [true, false]);
|
||||
await t.pumpAndSettle(const Duration(seconds: 3));
|
||||
});
|
||||
|
||||
testWidgets('117 (관리자 지시 10/20): 느린 원본 - 받는 동안은 넘기지 않고, 다 보인 뒤부터 정한 초를 잰다', (t) async {
|
||||
final c = controller();
|
||||
final sized = await source(t, [(100, 200), (100, 200), (100, 200)]);
|
||||
final src = _Slow(sized.images, 1);
|
||||
await open(t, c, src);
|
||||
await t.tap(find.byTooltip('계속 보기 (3초마다 다음 장)'));
|
||||
await t.pump();
|
||||
await t.pump(const Duration(seconds: 3));
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
// 2 장은 받는 중 - 한참 지나도 넘기지 않는다
|
||||
for (var k = 0; k < 10; k++) {
|
||||
await t.pump(const Duration(seconds: 1));
|
||||
}
|
||||
expect(find.text('2 / 3'), findsOneWidget, reason: '받는 동안 시계가 흐르지 않음');
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
// 다 받으면 그때부터 3초
|
||||
src.release.complete();
|
||||
await t.pump();
|
||||
await t.pump();
|
||||
await t.pump(const Duration(milliseconds: 2900));
|
||||
expect(find.text('2 / 3'), findsOneWidget);
|
||||
await t.pump(const Duration(milliseconds: 200));
|
||||
expect(find.text('3 / 3'), findsOneWidget);
|
||||
await t.tap(find.byTooltip('계속 보기 멈추기'));
|
||||
await t.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('117: 화면을 누르면 멈추고 그 누름으로는 넘기지 않음 · 손으로 밀어도 멈춤', (t) async {
|
||||
final c = controller();
|
||||
final src = await source(t, [(100, 200), (100, 200), (100, 200), (100, 200)]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue