Skip to content

Commit

Permalink
3d model working on flutter web (#80)
Browse files Browse the repository at this point in the history
* replaced web only imports with universal imports

* fixed macOS build not working

* created custom 3d viewer implementation for web
  • Loading branch information
DennisMoschina authored Nov 18, 2024
1 parent 9ebe512 commit bd9b602
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 35 deletions.
36 changes: 16 additions & 20 deletions open_earable/lib/sensor_data_tab/earable_3d_model.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:open_earable/sensor_data_tab/earable_3d_view.dart' if (dart.library.html) 'package:open_earable/sensor_data_tab/earable_3d_view_web.dart';
import 'dart:async';
import 'dart:math';
import 'package:open_earable_flutter/open_earable_flutter.dart';
import 'package:model_viewer_plus/model_viewer_plus.dart';
import 'package:webview_flutter/webview_flutter.dart';

class Earable3DModel extends StatefulWidget {
final OpenEarable openEarable;
Expand All @@ -14,15 +13,17 @@ class Earable3DModel extends StatefulWidget {
}

class _Earable3DModelState extends State<Earable3DModel> {
WebViewController? _controller;
StreamSubscription? _imuSubscription;
double _pitch = 0;
double _yaw = 0;
double _roll = 0;

final String fileName = "assets/OpenEarable.obj";
final GlobalKey<ModelViewerWidgetState> _modelViewerKey = GlobalKey();

final String fileName = 'assets/OpenEarableV1.glb';

dynamic sourceTexture;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -62,29 +63,24 @@ class _Earable3DModelState extends State<Earable3DModel> {
_pitch = data["EULER"]["PITCH"];
_roll = data["EULER"]["ROLL"];
});
_controller?.runJavaScript(
"document.querySelector('model-viewer').setAttribute('orientation', '${-_pitch} $_roll ${-_yaw}');",);
if (_modelViewerKey.currentState == null) {
print("ModelViewerKey.currentState is null");
return;
}
_modelViewerKey.currentState?.updateOrientation(_pitch, _yaw, _roll);
});
}

@override
Widget build(BuildContext context) {
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Expanded(
child: ModelViewer(
cameraControls: false,
backgroundColor: Theme.of(context).colorScheme.surface,
src: 'assets/OpenEarableV1.glb',
alt: 'A 3D model of an astronaut',
interactionPrompt: InteractionPrompt.none,
autoRotate: false,
disableZoom: true,
disablePan: true,
onWebViewCreated: (controller) {
_controller = controller;
controller.runJavaScript(
"document.body.style.overflow = 'hidden';document.documentElement.style.overflow = 'hidden';document.addEventListener('touchmove', function(e) { e.preventDefault(); }, { passive: false });",);
},),),
child: ModelViewerWidget(
key: _modelViewerKey,
modelSrc: fileName,
backgroundColor: Theme.of(context).colorScheme.surface,
),
),
Padding(
padding: EdgeInsets.only(bottom: 16),
child: Text(
Expand Down
58 changes: 58 additions & 0 deletions open_earable/lib/sensor_data_tab/earable_3d_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:model_viewer_plus/model_viewer_plus.dart';
import 'package:webview_flutter/webview_flutter.dart';

class ModelViewerWidget extends StatefulWidget {
final String modelSrc;
final Color backgroundColor;
final bool cameraControls;
final bool autoRotate;
final bool disableZoom;
final bool disablePan;

const ModelViewerWidget({
required this.modelSrc,
required this.backgroundColor,
this.cameraControls = false,
this.autoRotate = false,
this.disableZoom = true,
this.disablePan = true,
super.key,
});

@override
State<ModelViewerWidget> createState() => ModelViewerWidgetState();
}

class ModelViewerWidgetState extends State<ModelViewerWidget> {
WebViewController? _controller;

void updateOrientation(double pitch, double yaw, double roll) {
if (_controller != null) {
final jsCommand = "document.querySelector('model-viewer').setAttribute('orientation', '${-pitch} $roll ${-yaw}');";
_controller?.runJavaScript(jsCommand);
} else {
print("WebViewController is not initialized yet.");
}
}

@override
Widget build(BuildContext context) {
return ModelViewer(
cameraControls: widget.cameraControls,
backgroundColor: widget.backgroundColor,
src: widget.modelSrc,
alt: 'A 3D model of the OpenEarable',
interactionPrompt: InteractionPrompt.none,
autoRotate: widget.autoRotate,
disableZoom: widget.disableZoom,
disablePan: widget.disablePan,
onWebViewCreated: (controller) {
_controller = controller;
controller.runJavaScript(
"document.body.style.overflow = 'hidden';document.documentElement.style.overflow = 'hidden';document.addEventListener('touchmove', function(e) { e.preventDefault(); }, { passive: false });",
);
},
);
}
}
75 changes: 75 additions & 0 deletions open_earable/lib/sensor_data_tab/earable_3d_view_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'dart:ui_web';

import 'package:universal_html/html.dart' as html;
import 'package:flutter/material.dart';

class ModelViewerWidget extends StatefulWidget {
final String modelSrc;
final Color backgroundColor;
final bool cameraControls;
final bool autoRotate;
final bool disableZoom;
final bool disablePan;

const ModelViewerWidget({
required this.modelSrc,
required this.backgroundColor,
this.cameraControls = false,
this.autoRotate = false,
this.disableZoom = true,
this.disablePan = true,
super.key,
});

@override
State<ModelViewerWidget> createState() => ModelViewerWidgetState();
}

class ModelViewerWidgetState extends State<ModelViewerWidget> {
final String _viewType = 'model-viewer';

@override
void initState() {
super.initState();

platformViewRegistry.registerViewFactory(
_viewType,
(int viewId) {
final element = html.Element.tag('model-viewer')
..setAttribute('src', widget.modelSrc)
..setAttribute('alt', 'A 3D model of the OpenEarable')
..style.width = '100%'
..style.height = '100%';

if (widget.cameraControls) {
element.setAttribute('camera-controls', '');
}
if (widget.autoRotate) {
element.setAttribute('auto-rotate', '');
}
if (widget.disableZoom) {
element.setAttribute('disable-zoom', '');
}
if (widget.disablePan) {
element.setAttribute('disable-pan', '');
}

return element;
},
);
}

void updateOrientation(double pitch, double yaw, double roll) {
// Find the registered element and update its orientation
print("Updating orientation: $pitch, $yaw, $roll");
final element = html.document.querySelector(_viewType);
if (element != null) {
element.setAttribute('orientation', '${-pitch} $roll ${-yaw}');
}
}

@override
Widget build(BuildContext context) {
return HtmlElementView(viewType: _viewType);
}
}
8 changes: 2 additions & 6 deletions open_earable/lib/sensor_data_tab/sensor_html_chart.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:js' as js;
// ignore: avoid_web_libraries_in_flutter
import 'dart:html' as html;
import 'package:universal_html/html.dart' as html;
import 'package:universal_html/js.dart' as js;
import 'dart:ui_web';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:open_earable/sensor_data_tab/sensor_chart.dart';
// Conditional imports
// For web-specific code

class ChartSeries {
final String id;
Expand Down
5 changes: 3 additions & 2 deletions open_earable/lib/sensor_data_tab/sensor_html_chart_stub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ class ChartSeries {
});
}


class ChartJsWidget extends StatelessWidget {
final String chartType;
final List<ChartSeries> seriesList;

final String title;

const ChartJsWidget({
super.key,
const ChartJsWidget({super.key,
required this.chartType,
required this.seriesList,
required this.title,
Expand Down
32 changes: 26 additions & 6 deletions open_earable/macos/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
PODS:
- flutter_inappwebview_macos (0.0.1):
- FlutterMacOS
- OrderedSet (~> 5.0)
- OrderedSet (~> 6.0.3)
- FlutterMacOS (1.0.0)
- OrderedSet (5.0.0)
- open_file_mac (0.0.1):
- FlutterMacOS
- OrderedSet (6.0.3)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- universal_ble (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- open_file_mac (from `Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- universal_ble (from `Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)

SPEC REPOS:
trunk:
Expand All @@ -29,21 +40,30 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos
FlutterMacOS:
:path: Flutter/ephemeral
open_file_mac:
:path: Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
universal_ble:
:path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
webview_flutter_wkwebview:
:path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin

SPEC CHECKSUMS:
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
open_file_mac: 0e554648e2a87ce59e9438e3e5ca3e552e90d89a
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4

PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
16 changes: 16 additions & 0 deletions open_earable/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.dev"
source: hosted
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
Expand Down Expand Up @@ -787,6 +795,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_html:
dependency: "direct main"
description:
name: universal_html
sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
universal_io:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions open_earable/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ dependencies:
collection: ^1.18.0
webview_flutter: ^4.9.0
intl: ^0.18.1
universal_html: ^2.2.4

dependency_overrides:
intl: ^0.18.1
Expand Down
3 changes: 2 additions & 1 deletion open_earable/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<meta name="description" content="A new Flutter project.">

<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="open_earable">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
Expand All @@ -40,6 +40,7 @@
<link rel="stylesheet" type="text/css" href="splash/style.css">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<script src="splash/splash.js"></script>
<script type="module" src="./assets/packages/model_viewer_plus/assets/model-viewer.min.js" defer></script>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
Expand Down

0 comments on commit bd9b602

Please sign in to comment.