diff --git a/integration_test/browser_keep_test.dart b/integration_test/browser_keep_test.dart new file mode 100644 index 0000000..97ff79e --- /dev/null +++ b/integration_test/browser_keep_test.dart @@ -0,0 +1,82 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/app/bookmarks_controller.dart'; +import 'package:jj_mkvmaker/app/download_manager.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/browser_page.dart'; +import 'package:path/path.dart' as p; + +/// 실제 Edge(WebView2): MKV 화면 ↔ 브라우저를 여러 번 오가도 보던 페이지를 다시 읽지 않고 그대로 이어진다 +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('MKV 화면 ↔ 브라우저: 페이지 · 세션 유지', (tester) async { + var hits = 0; + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + server.listen((req) async { + if (req.uri.path == '/page') hits++; + req.response + ..headers.contentType = ContentType.html + ..write('JJ 유지유지'); + await req.response.close(); + }); + final url = 'http://127.0.0.1:${server.port}/page'; + final dataDir = Directory.systemTemp.createTempSync('jj_keep_').path; + + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings + ..webViewDataDir = dataDir + ..homeUrl = url; + final bm = BookmarksController(p.join(dataDir, 'bm.json')); + final d = DownloadManager(backends: const [], settings: () => c.settings, readClipboard: () async => null); + final navKey = GlobalKey(); + + await tester.pumpWidget(MaterialApp( + navigatorKey: navKey, + navigatorObservers: [browserRouteObserver], + home: const Scaffold(body: Text('MKV 화면')), + )); + Future wait(int ms) async { + await tester.runAsync(() => Future.delayed(Duration(milliseconds: ms))); + await tester.pump(); + } + + String address() { + final f = find.byType(TextField); + return f.evaluate().isEmpty ? '' : tester.widget(f.first).controller!.text; + } + + // 브라우저를 열어 페이지를 읽는다 + BrowserPage.open(navKey.currentState!, c: c, downloads: d, bookmarks: bm); + for (var i = 0; i < 100 && hits == 0; i++) { + await wait(100); + } + await wait(1000); + expect(hits, 1); + + for (var round = 1; round <= 3; round++) { + // MKV 화면으로 (브라우저 화면이 닫힌다) → 다시 브라우저 + navKey.currentState!.popUntil((r) => r.isFirst); + for (var i = 0; i < 10; i++) { + await wait(100); + } + expect(find.text('MKV 화면'), findsOneWidget); + BrowserPage.open(navKey.currentState!, c: c, downloads: d, bookmarks: bm); + for (var i = 0; i < 25; i++) { + await wait(100); + } + // ignore: avoid_print + print('RESULT $round번째: 주소 ${address()} · 페이지 요청 $hits번 · 브라우저 ${find.byType(BrowserPage).evaluate().length} · ' + '입력칸 ${find.byType(TextField).evaluate().length} · MKV ${find.text('MKV 화면').evaluate().length}'); + expect(address(), url, reason: '$round번째: 보던 주소'); + expect(hits, 1, reason: '$round번째: 페이지를 다시 읽지 않음 (같은 웹뷰)'); + } + await server.close(force: true); + }, timeout: const Timeout(Duration(minutes: 3))); +} diff --git a/lib/ui/browser_page.dart b/lib/ui/browser_page.dart index 69e6141..9e5b933 100644 --- a/lib/ui/browser_page.dart +++ b/lib/ui/browser_page.dart @@ -87,6 +87,13 @@ class BrowserPage extends StatefulWidget { /// 열려 있는 브라우저 화면 수 static int _open = 0; + /// 마지막으로 보던 주소: MKV 화면으로 갔다가 브라우저를 다시 열면 홈이 아니라 이 주소로 + static String? _lastUrl; + + /// 앱 안 웹뷰를 화면이 닫혀도 살려 둔다 (다시 열면 보던 페이지 · 재생 위치 · 뒤로 가기 기록 그대로) + static final _keepAlive = InAppWebViewKeepAlive(); + static bool _keepAliveUsed = false; + /// 웹 브라우저로: 이미 열린 브라우저가 있으면 그 화면으로 돌아가고 (보던 페이지 그대로), /// 없으면 새로 연다. 다운로드 목록 · 플레이어 등에서 브라우저 버튼을 눌러도 보던 페이지를 잃지 않는다. static Future open(NavigatorState nav, @@ -107,7 +114,7 @@ class BrowserPage extends StatefulWidget { } class _BrowserPageState extends State with RouteAware { - late String _url = widget.initialUrl ?? widget.c.settings.homeUrl; + late String _url = widget.initialUrl ?? BrowserPage._lastUrl ?? widget.c.settings.homeUrl; String _title = ''; double _progress = 1; bool _hasVideo = false, _canBack = false, _canFwd = false, _panel = false; @@ -123,6 +130,7 @@ class _BrowserPageState extends State with RouteAware { late final BrowserHost _host = BrowserHost( onUrl: (u) { if (!mounted || u.isEmpty) return; + BrowserPage._lastUrl = u; setState(() => _url = u); if (!_addressFocus.hasFocus) _address.text = u; }, @@ -160,6 +168,12 @@ class _BrowserPageState extends State with RouteAware { _nav?.pauseMedia(); } + /// MKV 화면 등으로 가며 브라우저 화면이 닫힌다: 살려 둔 웹뷰에서 소리만 나지 않게 멈춘다 + @override + void didPop() { + _nav?.pauseMedia(); + } + @override void dispose() { browserRouteObserver.unsubscribe(this); @@ -449,6 +463,24 @@ Future _createEnvironment(String dir) async { } class _EdgeViewState extends State<_EdgeView> { + /// 살려 둔 웹뷰를 이 화면이 쓰는지 (브라우저 화면은 하나뿐이지만, 혹시 둘이면 둘째는 새 웹뷰) + InAppWebViewKeepAlive? _keepAlive; + + @override + void initState() { + super.initState(); + if (!BrowserPage._keepAliveUsed) { + BrowserPage._keepAliveUsed = true; + _keepAlive = BrowserPage._keepAlive; + } + } + + @override + void dispose() { + if (_keepAlive != null) BrowserPage._keepAliveUsed = false; + super.dispose(); + } + @override Widget build(BuildContext context) => FutureBuilder( future: browserEnvironment(widget.dataDir), @@ -472,6 +504,7 @@ class _EdgeViewState extends State<_EdgeView> { } return InAppWebView( + keepAlive: _keepAlive, webViewEnvironment: snap.data, initialUrlRequest: URLRequest(url: WebUri(normalizeAddress(widget.initialUrl))), initialSettings: InAppWebViewSettings( @@ -479,7 +512,15 @@ class _EdgeViewState extends State<_EdgeView> { mediaPlaybackRequiresUserGesture: false, supportMultipleWindows: false, ), - onWebViewCreated: (c) => h.attach(_InAppNav(c, snap.data, widget.dataDir)), + onWebViewCreated: (c) async { + h.attach(_InAppNav(c, snap.data, widget.dataDir)); + // 살려 둔 웹뷰를 다시 붙였으면 보던 주소 · 제목 · 뒤로 가기 상태를 다시 알린다 + final u = await c.getUrl(); + if (u != null) h.onUrl(u.toString()); + final t = await c.getTitle(); + if (t != null) h.onTitle(t); + await check(c); + }, onLoadStart: (c, u) { h.onUrl(u?.toString() ?? ''); h.onVideo(false);