Skip to content

Commit

Permalink
Starting the macOS version. WIP.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgeffroy committed Aug 30, 2024
1 parent 64c92a2 commit 442adf1
Show file tree
Hide file tree
Showing 14 changed files with 477 additions and 211 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Iridium keeps the platform-specific code to the bare minimum (zero for now, in f
unified codebase. Until now, only the precompiled LCP DRM native library is specific to each platform, and is provided
in binary form by [Edrlab](https://edrlab.org) that simply must be dropped into the source tree.

## Supported Platforms

- **Android** 6 Marshmallow and later
- **iOS** 15 and later
- **macOS** 10.14 Mojave and later - *experimental*

## Features

Expand Down
246 changes: 180 additions & 66 deletions components/navigator/lib/src/epub/widget/webview_screen_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import 'package:flutter/material.dart' hide Decoration;
import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:mno_webview/webview.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
// #enddocregion platform_imports

import 'package:mno_navigator/epub.dart';
import 'package:mno_navigator/publication.dart';
import 'package:mno_navigator/src/epub/decoration.dart';
Expand All @@ -23,6 +25,12 @@ import 'package:mno_navigator/src/publication/model/annotation_type_and_idref_pr
import 'package:mno_server/mno_server.dart';
import 'package:mno_shared/publication.dart';
import 'package:universal_io/io.dart';
import 'package:webview_flutter/webview_flutter.dart' as webview_flutter;
// #docregion platform_imports
// Import for Android features.
import 'package:webview_flutter_android/webview_flutter_android.dart';
// Import for iOS/macOS features.
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

@protected
class WebViewScreenState extends State<WebViewScreen> {
Expand All @@ -48,6 +56,9 @@ class WebViewScreenState extends State<WebViewScreen> {
late StreamSubscription<List<String>> deletedAnnotationIdsSubscription;
late StreamSubscription<int> viewportWidthSubscription;

late final webview_flutter.WebViewController
_wefController; // Only needed for platforms where we use webview_flutter (MacOS)

bool isLoaded = false;

InAppWebViewController? _controller;
Expand Down Expand Up @@ -138,6 +149,95 @@ class WebViewScreenState extends State<WebViewScreen> {
});
viewportWidthSubscription = readerContext.viewportWidthStream
.listen((viewportWidth) => _jsApi?.setViewportWidth(viewportWidth));

initializeWebViewFlutterIfNeeded();
}

void initializeWebViewFlutterIfNeeded() {
if (Platform.isMacOS) {
// #docregion platform_features
late final webview_flutter.PlatformWebViewControllerCreationParams params;
if (webview_flutter.WebViewPlatform.instance is WebKitWebViewPlatform) {
params = WebKitWebViewControllerCreationParams(
allowsInlineMediaPlayback: true,
mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
);
} else {
params =
const webview_flutter.PlatformWebViewControllerCreationParams();
}

final webview_flutter.WebViewController controller =
webview_flutter.WebViewController.fromPlatformCreationParams(params);
// #enddocregion platform_features

controller
..setJavaScriptMode(webview_flutter.JavaScriptMode.unrestricted)
..setNavigationDelegate(
webview_flutter.NavigationDelegate(
onProgress: (int progress) {
debugPrint('WebView is loading (progress : $progress%)');
},
onPageStarted: (String url) {
debugPrint('Page started loading: $url');
},
onPageFinished: (String url) {
debugPrint('Page finished loading: $url');
},
onWebResourceError: (webview_flutter.WebResourceError error) {
debugPrint('''
Page resource error:
code: ${error.errorCode}
description: ${error.description}
errorType: ${error.errorType}
isForMainFrame: ${error.isForMainFrame}
''');
},
onNavigationRequest: (webview_flutter.NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
debugPrint('blocking navigation to ${request.url}');
return webview_flutter.NavigationDecision.prevent;
}
debugPrint('allowing navigation to ${request.url}');
return webview_flutter.NavigationDecision.navigate;
},
onHttpError: (webview_flutter.HttpResponseError error) {
debugPrint(
'Error occurred on page: ${error.response?.statusCode}');
},
onUrlChange: (webview_flutter.UrlChange change) {
debugPrint('url change to ${change.url}');
},
onHttpAuthRequest: (webview_flutter.HttpAuthRequest request) {
// openDialog(request);
},
),
)
..addJavaScriptChannel(
'Toaster',
onMessageReceived: (webview_flutter.JavaScriptMessage message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
},
)
..loadRequest(Uri.parse('https://flutter.dev'));

// setBackgroundColor is not currently supported on macOS.
if (kIsWeb || !Platform.isMacOS) {
controller.setBackgroundColor(const Color(0x80000000));
}

// #docregion platform_features
if (controller.platform is AndroidWebViewController) {
AndroidWebViewController.enableDebugging(true);
(controller.platform as AndroidWebViewController)
.setMediaPlaybackRequiresUserGesture(false);
}
// #enddocregion platform_features

_wefController = controller;
}
}

@override
Expand Down Expand Up @@ -165,72 +265,86 @@ class WebViewScreenState extends State<WebViewScreen> {
child: buildWebViewComponent(spineItem),
);

Widget buildWebViewComponent(Link link) => isLoaded
? InAppWebView(
key: _webViewKey,
initialUrlRequest: URLRequest(
url: WebUri('${widget.address}/${link.href.removePrefix("/")}')),
initialSettings: InAppWebViewSettings(
isInspectable: kDebugMode && Platform.isIOS,
isTextInteractionEnabled:
_viewerSettingsBloc.viewerSettings.isTextInteractionEnabled,
useHybridComposition: true,
useShouldInterceptRequest: true,
safeBrowsingEnabled: false,
cacheMode: CacheMode.LOAD_NO_CACHE,
disabledActionModeMenuItems: ActionModeMenuItem.MENU_ITEM_SHARE |
ActionModeMenuItem.MENU_ITEM_WEB_SEARCH |
ActionModeMenuItem.MENU_ITEM_PROCESS_TEXT,
useShouldOverrideUrlLoading: true,
verticalScrollBarEnabled: false,
horizontalScrollBarEnabled: false,
// Below a fix suggested by westernbuptboy in https://github.com/Mantano/iridium/issues/102
decelerationRate: ScrollViewDecelerationRate.FAST,
),
onConsoleMessage: (InAppWebViewController controller,
ConsoleMessage consoleMessage) {
Fimber.d(
"WebView[${consoleMessage.messageLevel}]: ${consoleMessage.message}");
},
shouldInterceptRequest: (InAppWebViewController controller,
WebResourceRequest request) async {
if (!_serverBloc.startHttpServer &&
request.url.toString().startsWith(_serverBloc.address)) {
_serverBloc
.onRequest(AndroidRequest(request))
.then((androidResponse) => androidResponse.response);
}
},
shouldOverrideUrlLoading: (controller, navigationAction) async =>
NavigationActionPolicy.ALLOW,
onLoadStop: _onPageFinished,
gestureRecognizers: {
Factory<WebViewHorizontalGestureRecognizer>(
() => webViewHorizontalGestureRecognizer),
// Suggested by markusait in https://github.com/Mantano/iridium/issues/108#issuecomment-1984986046
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
Factory<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer()),
},
contextMenu: ContextMenu(
options:
ContextMenuOptions(hideDefaultSystemContextMenuItems: true),
onCreateContextMenu: (hitTestResult) async {
_jsApi?.let((jsApi) async {
Selection? selection =
await jsApi.getCurrentSelection(currentLocator);
selection?.offset = webViewOffset();
selectionController.add(selection);
});
},
onHideContextMenu: () {
selectionController.add(null);
},
),
onWebViewCreated: _onWebViewCreated,
)
: const SizedBox.shrink();
Widget buildWebViewComponent(Link link) {
Fimber.d(
" ============ Platform.isMacOS || Platform.isWindows || Platform.isLinux --> ${Platform.isMacOS || Platform.isWindows || Platform.isLinux}");
if (Platform.isMacOS) {
_wefController.loadRequest(
Uri.parse('${widget.address}/${spineItem.href.removePrefix("/")}'));
}

return isLoaded
? (Platform.isMacOS
? webview_flutter.WebViewWidget(controller: _wefController)
: InAppWebView(
key: _webViewKey,
initialUrlRequest: URLRequest(
url: WebUri(
'${widget.address}/${link.href.removePrefix("/")}')),
initialSettings: InAppWebViewSettings(
isInspectable: kDebugMode && Platform.isIOS,
isTextInteractionEnabled: _viewerSettingsBloc
.viewerSettings.isTextInteractionEnabled,
useHybridComposition: true,
useShouldInterceptRequest: true,
safeBrowsingEnabled: false,
cacheMode: CacheMode.LOAD_NO_CACHE,
disabledActionModeMenuItems:
ActionModeMenuItem.MENU_ITEM_SHARE |
ActionModeMenuItem.MENU_ITEM_WEB_SEARCH |
ActionModeMenuItem.MENU_ITEM_PROCESS_TEXT,
useShouldOverrideUrlLoading: true,
verticalScrollBarEnabled: false,
horizontalScrollBarEnabled: false,
// Below a fix suggested by westernbuptboy in https://github.com/Mantano/iridium/issues/102
decelerationRate: ScrollViewDecelerationRate.FAST,
),
onConsoleMessage: (InAppWebViewController controller,
ConsoleMessage consoleMessage) {
Fimber.d(
"WebView[${consoleMessage.messageLevel}]: ${consoleMessage.message}");
},
shouldInterceptRequest: (InAppWebViewController controller,
WebResourceRequest request) async {
if (!_serverBloc.startHttpServer &&
request.url.toString().startsWith(_serverBloc.address)) {
_serverBloc
.onRequest(AndroidRequest(request))
.then((androidResponse) => androidResponse.response);
}
},
shouldOverrideUrlLoading:
(controller, navigationAction) async =>
NavigationActionPolicy.ALLOW,
onLoadStop: _onPageFinished,
gestureRecognizers: {
Factory<WebViewHorizontalGestureRecognizer>(
() => webViewHorizontalGestureRecognizer),
// Suggested by markusait in https://github.com/Mantano/iridium/issues/108#issuecomment-1984986046
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
Factory<LongPressGestureRecognizer>(
() => LongPressGestureRecognizer()),
},
contextMenu: ContextMenu(
options: ContextMenuOptions(
hideDefaultSystemContextMenuItems: true),
onCreateContextMenu: (hitTestResult) async {
_jsApi?.let((jsApi) async {
Selection? selection =
await jsApi.getCurrentSelection(currentLocator);
selection?.offset = webViewOffset();
selectionController.add(selection);
});
},
onHideContextMenu: () {
selectionController.add(null);
},
),
onWebViewCreated: _onWebViewCreated,
))
: const SizedBox.shrink();
}

void _onPageFinished(InAppWebViewController controller, Uri? url) async {
// Fimber.d("_onPageFinished[$position]: $url");
Expand Down
36 changes: 34 additions & 2 deletions components/navigator/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736
url: "https://pub.dev"
source: hosted
version: "4.9.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8"
url: "https://pub.dev"
source: hosted
version: "3.16.7"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
url: "https://pub.dev"
source: hosted
version: "2.10.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d"
url: "https://pub.dev"
source: hosted
version: "3.15.0"
xml:
dependency: transitive
description:
Expand All @@ -661,5 +693,5 @@ packages:
source: hosted
version: "3.1.1"
sdks:
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.10.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"
1 change: 1 addition & 0 deletions components/navigator/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies:
fimber: ^0.7.0
flutter_bloc: ^8.1.6
flutter_inappwebview: ^6.0.0
webview_flutter: ^4.9.0
flutter_spinkit: ^5.2.1
photo_view: ^0.15.0
preload_page_view: ^0.2.0
Expand Down
4 changes: 3 additions & 1 deletion demo-app/lib/view_models/details_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ class DetailsProvider extends ChangeNotifier {

Future downloadFile(
BuildContext context, State state, String url, String filename) async {
PermissionStatus permission = await Permission.storage.status;
PermissionStatus permission = Platform.isMacOS
? PermissionStatus.granted
: await Permission.storage.status;

if (permission != PermissionStatus.granted) {
await Permission.storage.request();
Expand Down
Loading

0 comments on commit 442adf1

Please sign in to comment.