Skip to content

Commit 55e0b8a

Browse files
committed
Enable web views on web.
1 parent b1f3b78 commit 55e0b8a

File tree

7 files changed

+121
-71
lines changed

7 files changed

+121
-71
lines changed

example_chat_bot/lib/chat_screen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class _ChatScreenState extends State<ChatScreen>
6666
firebaseProjectId: 'codeless-dev',
6767
firebaseCloudFunctionsBaseURL:
6868
'https://us-central1-codeless-dev.cloudfunctions.net',
69+
baseURL: 'https://dev.codelessly.com',
6970
// preload: false,
7071
),
7172
);

lib/src/codelessly_config.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ class CodelesslyConfig with EquatableMixin {
5454
/// Base URL of the Firebase Cloud Functions instance to use.
5555
final String firebaseCloudFunctionsBaseURL;
5656

57+
/// Base URL of the environment to use. Helpful for hosted video players.
58+
final String baseURL;
59+
5760
/// Creates a new instance of [CodelesslyConfig].
5861
///
5962
/// [authToken] is the token required to authenticate and initialize the SDK.
@@ -71,6 +74,7 @@ class CodelesslyConfig with EquatableMixin {
7174
this.preload = true,
7275
this.firebaseProjectId = defaultFirebaseProjectId,
7376
this.firebaseCloudFunctionsBaseURL = defaultFirebaseCloudFunctionsBaseURL,
77+
this.baseURL = defaultBaseURL,
7478
});
7579

7680
/// Creates a new instance of [CodelesslyConfig] with the provided optional
@@ -83,17 +87,19 @@ class CodelesslyConfig with EquatableMixin {
8387
bool? preload,
8488
String? firebaseProjectId,
8589
String? firebaseCloudFunctionsBaseURL,
90+
String? baseURL,
8691
}) =>
8792
CodelesslyConfig(
8893
authToken: authToken ?? this.authToken,
8994
slug: slug ?? this.slug,
90-
automaticallySendCrashReports: automaticallySendCrashReports ??
91-
this.automaticallySendCrashReports,
95+
automaticallySendCrashReports:
96+
automaticallySendCrashReports ?? this.automaticallySendCrashReports,
9297
isPreview: isPreview ?? this.isPreview,
9398
preload: preload ?? this.preload,
9499
firebaseProjectId: firebaseProjectId ?? this.firebaseProjectId,
95100
firebaseCloudFunctionsBaseURL:
96101
firebaseCloudFunctionsBaseURL ?? this.firebaseCloudFunctionsBaseURL,
102+
baseURL: baseURL ?? this.baseURL,
97103
);
98104

99105
@override
@@ -105,6 +111,7 @@ class CodelesslyConfig with EquatableMixin {
105111
preload,
106112
firebaseProjectId,
107113
firebaseCloudFunctionsBaseURL,
114+
baseURL,
108115
];
109116
}
110117

lib/src/constants.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@ const String defaultFirebaseCloudFunctionsBaseURL = String.fromEnvironment(
88
defaultValue: prodFirebaseCloudFunctionsBaseURL,
99
);
1010

11+
const String defaultBaseURL = String.fromEnvironment(
12+
'base_url',
13+
defaultValue: prodBaseUrl,
14+
);
15+
1116
const String prodFirebaseProjectId = 'codeless-app';
1217
const String prodFirebaseCloudFunctionsBaseURL =
1318
'https://us-central1-codeless-app.cloudfunctions.net';
1419

20+
const String prodBaseUrl = 'https://app.codelessly.com';
21+
1522
const defaultErrorMessage =
1623
'We encountered some error while rendering this page! '
1724
'We are working on it to fix it as soon as possible.';

lib/src/transformers/node_transformers/passive_embedded_video_transformer.dart

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import 'package:codelessly_api/codelessly_api.dart';
2+
import 'package:flutter/foundation.dart';
23
import 'package:flutter/material.dart';
34
import 'package:google_fonts/google_fonts.dart';
5+
import 'package:provider/provider.dart';
46
import 'package:webview_flutter/webview_flutter.dart';
57
import 'package:webview_flutter_android/webview_flutter_android.dart';
8+
import 'package:webview_flutter_web/webview_flutter_web.dart';
69
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
710

811
import '../../../codelessly_sdk.dart';
@@ -84,24 +87,31 @@ class _PassiveEmbeddedVideoWidgetState
8487
void initState() {
8588
super.initState();
8689
final PlatformWebViewControllerCreationParams params;
87-
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
88-
params = WebKitWebViewControllerCreationParams(
89-
allowsInlineMediaPlayback: true,
90-
mediaTypesRequiringUserAction: {
91-
if (!widget.node.properties.autoPlay) ...{
92-
PlaybackMediaTypes.audio,
93-
PlaybackMediaTypes.video,
94-
},
95-
},
96-
);
97-
} else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
98-
params = AndroidWebViewControllerCreationParams();
90+
if (kIsWeb) {
91+
// WebView on web only supports loadRequest. Any other method invocation
92+
// on the controller will result in an exception. Be aware!!
93+
WebViewPlatform.instance = WebWebViewPlatform();
94+
_controller = WebViewController();
9995
} else {
100-
params = const PlatformWebViewControllerCreationParams();
101-
}
96+
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
97+
params = WebKitWebViewControllerCreationParams(
98+
allowsInlineMediaPlayback: true,
99+
mediaTypesRequiringUserAction: {
100+
if (!widget.node.properties.autoPlay) ...{
101+
PlaybackMediaTypes.audio,
102+
PlaybackMediaTypes.video,
103+
},
104+
},
105+
);
106+
} else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
107+
params = AndroidWebViewControllerCreationParams();
108+
} else {
109+
params = const PlatformWebViewControllerCreationParams();
110+
}
102111

103-
_controller = WebViewController.fromPlatformCreationParams(params);
104-
_controller.setJavaScriptMode(JavaScriptMode.unrestricted);
112+
_controller = WebViewController.fromPlatformCreationParams(params);
113+
_controller.setJavaScriptMode(JavaScriptMode.unrestricted);
114+
}
105115
// _controller.setUserAgent('chrome');
106116
if (_controller.platform is AndroidWebViewController) {
107117
print('setting android config');
@@ -121,20 +131,25 @@ class _PassiveEmbeddedVideoWidgetState
121131
// controller.setUserAgent('chrome');
122132
}
123133
}
134+
135+
final config = context.read<CodelesslyConfig>();
136+
124137
final String embedUrl;
125138
switch (widget.node.properties.source) {
126139
case EmbeddedVideoSource.youtube:
127140
embedUrl = buildYoutubeEmbedUrl(
128141
properties: widget.node.properties as EmbeddedYoutubeVideoProperties,
129142
width: widget.node.basicBoxLocal.width,
130143
height: widget.node.basicBoxLocal.height,
144+
baseUrl: config.baseURL,
131145
);
132146
break;
133147
case EmbeddedVideoSource.vimeo:
134148
embedUrl = buildVimeoEmbedUrl(
135149
properties: widget.node.properties as EmbeddedVimeoVideoProperties,
136150
width: widget.node.basicBoxLocal.width,
137151
height: widget.node.basicBoxLocal.height,
152+
baseUrl: config.baseURL,
138153
);
139154
break;
140155
}
@@ -163,7 +178,8 @@ class _PassiveEmbeddedVideoWidgetState
163178
Widget buildEmbeddedYoutubeVideo(
164179
BuildContext context, EmbeddedYoutubeVideoProperties properties) {
165180
if (PassiveEmbeddedVideoWidget.supportedPlatforms
166-
.contains(Theme.of(context).platform)) {
181+
.contains(Theme.of(context).platform) ||
182+
kIsWeb) {
167183
return WebViewWidget(
168184
controller: _controller,
169185
key: ValueKey(properties),
@@ -190,7 +206,8 @@ class _PassiveEmbeddedVideoWidgetState
190206
Widget buildEmbeddedVimeoVideo(
191207
BuildContext context, EmbeddedVimeoVideoProperties properties) {
192208
if (PassiveEmbeddedVideoWidget.supportedPlatforms
193-
.contains(Theme.of(context).platform)) {
209+
.contains(Theme.of(context).platform) ||
210+
kIsWeb) {
194211
return WebViewWidget(
195212
key: ValueKey(properties),
196213
controller: _controller,
@@ -217,12 +234,14 @@ class _PassiveEmbeddedVideoWidgetState
217234

218235
@override
219236
void dispose() {
220-
// _controller doesn't have a way to dispose the player, so we call
221-
// js methods directly.
222-
if (widget.node.properties.source == EmbeddedVideoSource.youtube) {
223-
_controller.runJavaScript('player.stopVideo();');
224-
} else {
225-
_controller.runJavaScript('player.pause();');
237+
if (!kIsWeb) {
238+
// _controller doesn't have a way to dispose the player, so we call
239+
// js methods directly.
240+
if (widget.node.properties.source == EmbeddedVideoSource.youtube) {
241+
_controller.runJavaScript('player.stopVideo();');
242+
} else {
243+
_controller.runJavaScript('player.pause();');
244+
}
226245
}
227246

228247
super.dispose();

lib/src/transformers/node_transformers/passive_web_view_transformer.dart

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
88
import 'package:url_launcher/url_launcher.dart';
99
import 'package:webview_flutter/webview_flutter.dart';
1010
import 'package:webview_flutter_android/webview_flutter_android.dart';
11+
import 'package:webview_flutter_web/webview_flutter_web.dart';
1112
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
1213

1314
import '../../../codelessly_sdk.dart';
@@ -87,40 +88,49 @@ class _PassiveWebViewWidgetState extends State<PassiveWebViewWidget> {
8788
super.initState();
8889
final props = widget.node.properties;
8990

90-
final PlatformWebViewControllerCreationParams params;
91-
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
92-
params = WebKitWebViewControllerCreationParams(
93-
allowsInlineMediaPlayback: props.allowsInlineMediaPlayback == true,
94-
mediaTypesRequiringUserAction: {
95-
if (props.mediaAutoPlaybackPolicy !=
96-
WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia) ...{
97-
PlaybackMediaTypes.audio,
98-
PlaybackMediaTypes.video,
99-
},
100-
},
101-
);
102-
} else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
103-
params = AndroidWebViewControllerCreationParams();
91+
if (kIsWeb) {
92+
// WebView on web only supports loadRequest. Any other method invocation
93+
// on the controller will result in an exception. Be aware!!
94+
WebViewPlatform.instance = WebWebViewPlatform();
95+
_controller = WebViewController();
10496
} else {
105-
params = const PlatformWebViewControllerCreationParams();
106-
}
107-
108-
_controller = WebViewController.fromPlatformCreationParams(params);
109-
_controller.setJavaScriptMode(JavaScriptMode.unrestricted);
110-
if (_controller.platform is AndroidWebViewController) {
111-
(_controller.platform as AndroidWebViewController)
112-
.setMediaPlaybackRequiresUserGesture(props.mediaAutoPlaybackPolicy !=
113-
WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia);
97+
final PlatformWebViewControllerCreationParams params;
98+
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
99+
params = WebKitWebViewControllerCreationParams(
100+
allowsInlineMediaPlayback: props.allowsInlineMediaPlayback == true,
101+
mediaTypesRequiringUserAction: {
102+
if (props.mediaAutoPlaybackPolicy !=
103+
WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia) ...{
104+
PlaybackMediaTypes.audio,
105+
PlaybackMediaTypes.video,
106+
},
107+
},
108+
);
109+
} else if (WebViewPlatform.instance is AndroidWebViewPlatform) {
110+
params = AndroidWebViewControllerCreationParams();
111+
} else {
112+
params = const PlatformWebViewControllerCreationParams();
113+
}
114+
115+
_controller = WebViewController.fromPlatformCreationParams(params);
116+
_controller.setJavaScriptMode(JavaScriptMode.unrestricted);
117+
118+
if (_controller.platform is AndroidWebViewController) {
119+
(_controller.platform as AndroidWebViewController)
120+
.setMediaPlaybackRequiresUserGesture(
121+
props.mediaAutoPlaybackPolicy !=
122+
WebViewMediaAutoPlaybackPolicy.alwaysPlayAllMedia);
123+
}
124+
125+
// Using this user-agent string to force the video to play in the webview
126+
// on Android. This is a hack, but it works.
127+
// _controller.setUserAgent(
128+
// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36');
129+
130+
_controller.setBackgroundColor(
131+
props.backgroundColor?.toFlutterColor() ?? Colors.transparent);
114132
}
115133

116-
// Using this user-agent string to force the video to play in the webview
117-
// on Android. This is a hack, but it works.
118-
// _controller.setUserAgent(
119-
// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36');
120-
121-
_controller.setBackgroundColor(
122-
props.backgroundColor?.toFlutterColor() ?? Colors.transparent);
123-
124134
_loadData();
125135
}
126136

@@ -178,7 +188,8 @@ class _PassiveWebViewWidgetState extends State<PassiveWebViewWidget> {
178188
Widget buildWebpageWebView(
179189
BuildContext context, WebPageWebViewProperties properties) {
180190
if (!PassiveWebViewWidget.supportedPlatforms
181-
.contains(Theme.of(context).platform)) {
191+
.contains(Theme.of(context).platform) &&
192+
!kIsWeb) {
182193
return WebViewPreviewWidget(
183194
icon: Icon(Icons.language_rounded),
184195
node: widget.node,
@@ -203,7 +214,8 @@ class _PassiveWebViewWidgetState extends State<PassiveWebViewWidget> {
203214
Widget buildGoogleMapsWebView(
204215
BuildContext context, GoogleMapsWebViewProperties properties) {
205216
if (!PassiveWebViewWidget.supportedPlatforms
206-
.contains(Theme.of(context).platform)) {
217+
.contains(Theme.of(context).platform) &&
218+
!kIsWeb) {
207219
return WebViewPreviewWidget(
208220
icon: Icon(Icons.map_outlined),
209221
node: widget.node,
@@ -219,7 +231,8 @@ class _PassiveWebViewWidgetState extends State<PassiveWebViewWidget> {
219231
Widget buildTwitterWebView(
220232
BuildContext context, TwitterWebViewProperties properties) {
221233
if (!PassiveWebViewWidget.supportedPlatforms
222-
.contains(Theme.of(context).platform)) {
234+
.contains(Theme.of(context).platform) &&
235+
!kIsWeb) {
223236
return WebViewPreviewWidget(
224237
icon: ImageIcon(
225238
NetworkImage('https://img.icons8.com/color/344/twitter--v2.png')),

lib/src/transformers/transformer_functions.dart

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ String buildYoutubeEmbedUrl({
350350
required EmbeddedYoutubeVideoProperties properties,
351351
required double? width,
352352
required double? height,
353-
String? baseUrl,
353+
required String baseUrl,
354354
}) {
355355
final Map<String, String> queryParams = {
356356
'video_id': properties.videoId ?? '<video_id>',
@@ -368,7 +368,7 @@ String buildYoutubeEmbedUrl({
368368
'caption_lang': properties.captionLanguage,
369369
};
370370

371-
final baseUri = Uri.parse(getBaseUrl(properties.source, baseUrl));
371+
final baseUri = Uri.parse(getVideoUrl(properties.source, baseUrl));
372372
final String url = Uri(
373373
scheme: baseUri.scheme,
374374
host: baseUri.host,
@@ -383,7 +383,7 @@ String buildVimeoEmbedUrl({
383383
required EmbeddedVimeoVideoProperties properties,
384384
required double? width,
385385
required double? height,
386-
String? baseUrl,
386+
required String baseUrl,
387387
}) {
388388
final Map<String, String> queryParams = {
389389
'video_id': properties.videoId ?? '<video_id>',
@@ -395,7 +395,7 @@ String buildVimeoEmbedUrl({
395395
if (height != null) 'height': '${height.toInt()}',
396396
};
397397

398-
final baseUri = Uri.parse(getBaseUrl(properties.source, baseUrl));
398+
final baseUri = Uri.parse(getVideoUrl(properties.source, baseUrl));
399399
final String url = Uri(
400400
scheme: baseUri.scheme,
401401
host: baseUri.host,
@@ -408,15 +408,17 @@ String buildVimeoEmbedUrl({
408408
}
409409

410410
/// [baseUrl] param allows to override the base url of the video.
411-
String getBaseUrl(EmbeddedVideoSource source, [String? baseUrl]) {
412-
String updatedBaseUrl;
413-
if (baseUrl != null) {
414-
updatedBaseUrl = baseUrl;
415-
} else if (kIsWeb) {
411+
String getVideoUrl(EmbeddedVideoSource source, String baseUrl) {
412+
String updatedBaseUrl = baseUrl;
413+
if (kIsWeb && !kReleaseMode) {
414+
// This allows to test and debug video player scripts in local environment.
416415
var url = Uri.parse(window.location.href);
417416
updatedBaseUrl = url.origin;
418-
} else {
419-
updatedBaseUrl = 'https://dev.codelessly.com';
417+
}
418+
419+
// Remove trailing slash if any.
420+
if (updatedBaseUrl.endsWith('/')) {
421+
updatedBaseUrl = updatedBaseUrl.substring(0, updatedBaseUrl.length - 1);
420422
}
421423

422424
switch (source) {

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies:
3131
webview_flutter: ^4.2.4
3232
webview_flutter_android: ^3.9.5
3333
webview_flutter_wkwebview: ^3.7.4
34+
webview_flutter_web: ^0.2.2+3
3435
flutter_animation_progress_bar: ^2.3.1
3536
vector_math: ^2.1.4
3637
grpc: ^3.2.3

0 commit comments

Comments
 (0)