Skip to content

Commit 0d014c8

Browse files
committed
feat: implement webview functionality for desktop and mobile, enhance image widget to support data URLs
1 parent b2bfa7a commit 0d014c8

8 files changed

Lines changed: 123 additions & 26 deletions

File tree

lib/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ Future<void> loadMiruCore() async {
201201
"dbname": p.join(appSupportDir, 'miru.db'),
202202
"sslmode": "disable",
203203
},
204-
"cookieStoreLocation": Platform.isAndroid ? appSupportDir : "",
204+
"cookieStoreLocation": Platform.isAndroid
205+
? p.join(appSupportDir, "cookies")
206+
: "",
205207
"extensionPath": p.join(appSupportDir, 'extensions'),
206208
};
207209

lib/pages/detail/detail_page.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,7 +1589,7 @@ class DetailHeaderDelegate extends SliverPersistentHeaderDelegate {
15891589
constraints: const BoxConstraints(maxWidth: 1500),
15901590
child: (isLoading)
15911591
? _DesktopLoadingWidgetExtended()
1592-
: _DesktopWidgetExtended(
1592+
: DesktopWebView(
15931593
meta: meta,
15941594
detailUrl: detailUrl,
15951595
detail: detail,
@@ -1817,8 +1817,9 @@ class _DownloadDialogState extends State<_DownloadDialog>
18171817
}
18181818
}
18191819

1820-
class _DesktopWidgetExtended extends StatelessWidget {
1821-
const _DesktopWidgetExtended({
1820+
class DesktopWebView extends StatelessWidget {
1821+
const DesktopWebView({
1822+
super.key,
18221823
required this.meta,
18231824
this.detailUrl,
18241825
this.detail,

lib/pages/search/desktop_search_page.dart

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:desktop_webview_window/desktop_webview_window.dart';
15
import 'package:flutter/material.dart';
26
import 'package:flutter_hooks/flutter_hooks.dart';
37
import 'package:forui/forui.dart';
48
import 'package:hooks_riverpod/hooks_riverpod.dart';
9+
import 'package:miru_app_new/pages/webview/desktop_webview.dart';
510
import 'package:miru_app_new/provider/extension_page_notifier_provider.dart';
611
import 'package:miru_app_new/utils/core/log.dart';
712
import 'package:miru_app_new/utils/store/storage_index.dart';
@@ -86,24 +91,35 @@ class DesktopSearchPage extends HookConsumerWidget {
8691
final ext = metaData[index];
8792
return DesktopSearchListTile(
8893
ext: ext,
89-
trailing: FButton.icon(
90-
selected: true,
91-
onPress: () {
92-
final newSet = {...pinnedExtensions.value};
93-
if (newSet.contains(ext.packageName)) {
94-
newSet.remove(ext.packageName);
95-
} else {
96-
newSet.add(ext.packageName);
97-
}
98-
pinnedExtensions.value = newSet;
99-
MiruSettings.setSettingSync(
100-
SettingKey.pinnedExtension,
101-
newSet.toString(),
102-
);
103-
},
104-
child: pinnedExtensions.value.contains(ext.packageName)
105-
? Icon(FIcons.pinOff)
106-
: Icon(FIcons.pin),
94+
trailing: Row(
95+
children: [
96+
FButton.icon(
97+
style: FButtonStyle.ghost(),
98+
onPress: () => openWebview(ext),
99+
child: Icon(FIcons.globe),
100+
),
101+
SizedBox(width: 8),
102+
FButton.icon(
103+
selected: true,
104+
onPress: () {
105+
final newSet = {...pinnedExtensions.value};
106+
if (newSet.contains(ext.packageName)) {
107+
newSet.remove(ext.packageName);
108+
} else {
109+
newSet.add(ext.packageName);
110+
}
111+
pinnedExtensions.value = newSet;
112+
MiruSettings.setSettingSync(
113+
SettingKey.pinnedExtension,
114+
newSet.toString(),
115+
);
116+
},
117+
child:
118+
pinnedExtensions.value.contains(ext.packageName)
119+
? Icon(FIcons.pinOff)
120+
: Icon(FIcons.pin),
121+
),
122+
],
107123
),
108124
);
109125
},
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:desktop_webview_window/desktop_webview_window.dart';
5+
import 'package:miru_app_new/model/extension_meta_data.dart';
6+
import 'package:miru_app_new/utils/core/log.dart';
7+
8+
Future<void> openWebview(ExtensionMeta ext) async {
9+
final url = ext.webSite;
10+
final webview =
11+
await WebviewWindow.create(
12+
configuration: CreateConfiguration(
13+
windowHeight: 1280,
14+
windowWidth: 720,
15+
title: "ExampleTestWindow",
16+
titleBarTopPadding: Platform.isMacOS ? 20 : 0,
17+
// userDataFolderWindows: await _getWebViewPath(),
18+
),
19+
)
20+
..launch(url);
21+
final timer = Timer.periodic(const Duration(seconds: 1), (timer) async {
22+
try {
23+
final cookies = await webview.getAllCookies();
24+
25+
if (cookies.isEmpty) {
26+
logger.info('⚠️ no cookies found');
27+
}
28+
} catch (e, stack) {
29+
logger.info('getAllCookies error: $e');
30+
logger.info(stack);
31+
}
32+
});
33+
webview.onClose.whenComplete(() async {
34+
logger.info("on close");
35+
timer.cancel();
36+
37+
// timer.cancel();
38+
});
39+
}

lib/utils/router/router_util.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import '../../../pages/home/history_page.dart';
1010
import '../../../pages/index.dart';
1111
import '../../../pages/main_page.dart';
1212
import '../../pages/watch/manga_reader.dart';
13-
import '../../../pages/mobile_webview.dart';
13+
import '../../pages/webview/mobile_webview.dart';
1414
import '../../pages/watch/novel_reader.dart';
1515
import '../../../pages/search_page_single_view.dart';
1616
import '../../pages/watch/video_player.dart';

lib/widgets/core/image_widget.dart

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:convert';
12
import 'package:extended_image/extended_image.dart';
23
import 'package:flutter/material.dart';
34
import 'package:forui/forui.dart';
@@ -16,14 +17,52 @@ class ImageWidget extends StatelessWidget {
1617
final BoxFit? fit;
1718
@override
1819
Widget build(BuildContext context) {
20+
// Support data URLs (base64) and network URLs.
21+
final trimmed = imageUrl.trim();
22+
final isDataUri = trimmed.startsWith('data:image/');
23+
24+
if (isDataUri) {
25+
try {
26+
// data:[<mediatype>][;base64],<data>
27+
final comma = trimmed.indexOf(',');
28+
final base64Str = (comma >= 0) ? trimmed.substring(comma + 1) : trimmed;
29+
final bytes = base64Decode(base64Str);
30+
31+
return ExtendedImage.memory(
32+
bytes,
33+
borderRadius: BorderRadius.circular(10),
34+
shape: BoxShape.rectangle,
35+
width: width,
36+
height: height,
37+
fit: fit,
38+
loadStateChanged: (ExtendedImageState state) {
39+
switch (state.extendedImageLoadState) {
40+
case LoadState.loading:
41+
return const Center(child: CircularProgressIndicator());
42+
case LoadState.completed:
43+
return null;
44+
case LoadState.failed:
45+
return const Icon(FIcons.cloudAlert);
46+
}
47+
},
48+
);
49+
} catch (e) {
50+
return SizedBox(
51+
width: width,
52+
height: height,
53+
child: const Icon(FIcons.cloudAlert),
54+
);
55+
}
56+
}
57+
58+
// Fallback: treat as network image
1959
return ExtendedImage.network(
60+
imageUrl,
2061
borderRadius: BorderRadius.circular(10),
2162
shape: BoxShape.rectangle,
2263
width: width,
2364
height: height,
24-
cache: true,
2565
fit: fit,
26-
imageUrl,
2766
loadStateChanged: (ExtendedImageState state) {
2867
switch (state.extendedImageLoadState) {
2968
case LoadState.loading:

0 commit comments

Comments
 (0)