YouTube video height is too small in WebView Jetpack Compose - android-webview

I am playing around with Jetpack Compose, so I created an sample app that has only one webview that loads the YouTube URL. and When I play a video the height of the video is too small.
#Composable
fun MyScreen() {
val context = LocalContext.current
Surface {
AndroidView(
factory = {
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
}
},
update = {
it.loadUrl("https://youtube.com")
},
modifier = Modifier.fillMaxSize()
)
}
}

You can set a custom aspect ratio to your layout by Modifier.aspectRatio(16 / 9f) (16:9 is the most common video aspect ratio).
Or if you want to see more content from the WebView you can just make the size larger or make it fillMaxSize().
If you actually want to create an app that uses YouTube webviews, I suggest looking at this library both for code reference and potentially just using it.

Also had this problem. As pointed by #Pylyp Dukhov, it doesn't happens when using XML. So what I did, for now, is inflate a XML with a WebView inside a Frame layout an pass that as the Android view's factory's return

I had the same problem. I still don't know why it behaves like that (I guess it's a bug) but in the end I got it working by using the Google Accompanist WebView.
val state = rememberWebViewState("https://youtube.com")
WebView(
modifier = Modifier.fillMaxSize(),
state = state
)
Here is a full example.
Edit:
Looking at the Accompanist WebView source code, you only have to set the WebView height and width to MATCH_PARENT. Like this:
AndroidView(
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
loadUrl("https://youtube.com")
}
}
)

Related

Is there a way to query if a WebView is scrollable?

I am using a PreloadPageView to display WebViews whose content vary in length. I would like to be able to inform the user when there is more content below on WebViews which are scrollable. I have played around with the controller scroll methods and with some JS evaluations, but to no avail. Wish there was a controller.isScrollable() method!
You can get the DOM height.
Call this in the webview' s onLoadStop function:
controller
.evaluateJavascript(source: '''(() => { return document.documentElement.scrollHeight;})()''')
.then((scrollHeight) {
final widgetHeight = _webViewKey.currentContext?.size?.height ?? 0;
// compare scrollHeight to widgetHeight
});

How to get notified when my element has been attached to the DOM

I'm adding a custom control that uses SVG to a Google Map.
After the map has been loaded and my control is shown, I need to grab the BBox from the svg element. Since I do not control when my element is attached to the DOM, I'm trying to find an event that will allow me to do the work in a callback.
Here's roughly what I have:
map = new google.maps.Map(...);
...
container = document.createElement("div")
svg = createAndDrawSVGElement(...); //this returns an svg element
container.appendChild(svg);
INSERT_THE_RIGHT_EVENT_HERE(function() {
var bbox = svg.getBBox();
... //bbox will be empty if svg isn't attached
}
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(container);
My current, ugly workaround is a setTimeout. I'd like something more predictable.
I was able to resolve this without an event, by attaching my SVG to a hidden DIV temporarily, so I can get the bbox.
Solution here: https://stackoverflow.com/a/45465286/62024
After looking at the API, I would try this (although the doc does not explicitely say whether this is called before or after attaching the element):
var map = new google.maps.Map(...);
...
var container = document.createElement("div")
var svg = createAndDrawSVGElement(...); //this returns an svg element
container.appendChild(svg);
var controls = map.controls[google.maps.ControlPosition.RIGHT_BOTTOM];
var position = controls.length;
google.maps.event.addListenerOnce(controls, "insert_at", function(i) {
if (i === position) {
var bbox = svg.getBBox();
... //etc
}
}
controls.insertAt(pos, container);

Binding Image URIs via Image.SetBinding() not working properly

I'm using the following code snippet to bind a image url to a image object (used as one element in a ViewCell object).
...
Image Picture = new Image()
{
Aspect = Aspect.AspectFit,
HorizontalOptions = LayoutOptions.StartAndExpand
}; // ImageSource.FromUri(new Uri("sImageUrl")))
Picture.SetBinding(Image.SourceProperty, "sImageUrl");
...
The images in the list, for which I'm using that cell view, are not loading always. I was not able to identify the exact reason for the problem, but I think the problem is the load-process (for loading the images from the url/internet)..
Maybe the problem could be solved by setting the url via new Uri(...) as described in the documentation
var webImage = new Image { Aspect = Aspect.AspectFit };
webImage.Source = ImageSource.FromUri(new Uri("https://xamarin.com/content/images/pages/forms/example-app.png"));
Now my question: is there a workaround for binding a uri object? e.g.
...
Image Picture = new Image()
{
Aspect = Aspect.AspectFit,
HorizontalOptions = LayoutOptions.StartAndExpand
}; // ImageSource.FromUri(new Uri("sImageUrl")))
Picture.SetBinding(Image.SourceProperty, ImageSource.FromUri(new Uri("sImageUrl")));
...
I'm working with xamarin studio (version 6.1.2, build 44, update channel "beta", os x).
Would be great if someone got a tipp.
Thanks a lot,
Alex
after some more trying I found a solution.. I'm not sure if it's a secure / professional way.. But for now, it seems to work.
What I did: I implemented my own class "AsyncSrcImage", derived it from Image and added another bindable property:
public class AsyncSrcImage : Image
{
private static String sDefaultURL = "";
public static readonly BindableProperty AsyncImgUrlProperty = BindableProperty.Create(nameof(AsyncImgUrl), typeof(String), typeof(AsyncSrcImage), sDefaultURL);
public String AsyncImgUrl
{
get { return (String)GetValue(AsyncImgUrlProperty); }
set { SetValue(AsyncImgUrlProperty, value); }
}
}
Additionally I adjusted the rendering process with a custom renderer. There I'm loading the image source via new Uri(...) as described in the documentation:
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using System;
[assembly: ExportRenderer(typeof(AsyncSrcImage), typeof(AsyncSrcImageRenderer))]
public class AsyncSrcImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
AsyncSrcImage oImage = (AsyncSrcImage)Element;
if (oImage != null && oImage.AsyncImgUrl != "")
{
oImage.Source = ImageSource.FromUri(new Uri(oImage.AsyncImgUrl));
}
}
}
Maybe that helps other folks ;)

Need to create xul based frame in firefox extension build using xul

I want to create xul based frame in one of my firefox extension. The frame should look like https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/ui_frame
or
how to use below node js code in xul:
var { Frame } = require("sdk/ui/frame");
var frame = new Frame({
url: "./city-info.html"
});
In node.js, its working fine, but I dont know how to create the same thing with xul. Anybody can help?
thanks in advance.
You have provided very little detail as to what you desire. If all that you desire is to create an <iframe> in which you can load HTML content, then something along the lines of the following will do so:
//The URL of the HTML you desire to load.
let chromeUrl = '[Some URL here]';
//Whatever element you want the iframe placed under.
let parentEl = document.getElementById('foo');
//* Overlay and bootstrap (from almost any context/scope):
Components.utils.import('resource://gre/modules/Services.jsm');//Services
let activeWindow = Services.wm.getMostRecentWindow('navigator:browser');
//*/
let mainDocument = activeWindow.document;
//Create the <iframe> use
//mainDocument for the XUL namespace.
let iframeEl;
if(options.useBrowser){
iframeEl = mainDocument.createElement('browser');
} else {
iframeEl = mainDocument.createElement('iframe');
}
iframeEl.id = id;
iframeEl.setAttribute('src',chromeUrl);
iframeEl.setAttribute("tooltip", "aHTMLTooltip");
iframeEl.setAttribute("autocompleteenabled", true);
iframeEl.setAttribute("autocompletepopup", "PopupAutoComplete");
iframeEl.setAttribute("disablehistory",true);
iframeEl.setAttribute('type', 'content');
parentEl.appendChild(iframeEl);
The above code was taken from my answer to Firefox SDK Add-on with a sidebar on both the right and left at the same time, which creates sidebars. One option of how those sidebars are created is to have them contain an <iframe>.
Finally I got the answer:
let chromeUrl = 'YOUR HTML PAGE URL';
Components.utils.import('resource://gre/modules/Services.jsm');//Services
let activeWindow = Services.wm.getMostRecentWindow('navigator:browser');
//*/
let mainDocument = activeWindow.document;
let iframeEl;
iframeEl = mainDocument.createElement('iframe');
iframeEl.id = "d";
iframeEl.setAttribute('src',chromeUrl);
iframeEl.setAttribute("tooltip", "aHTMLTooltip");
iframeEl.setAttribute("autocompleteenabled", true);
iframeEl.setAttribute("autocompletepopup", "PopupAutoComplete");
iframeEl.setAttribute("disablehistory",true);
iframeEl.setAttribute('type', 'content');
iframeEl.setAttribute('height', '32px');
window.document.documentElement.appendChild(iframeEl);

Make Webview's auto links visible

A Webview will display links in the content HTML as having blue underlines. So if you have something in the HTML like
blah blah
... it is clearly visible as a link.
The Webview also allows you to click on phone numbers and addresses (even if those are just text in the HTML, not links) to launch the Dialer or Maps.
How can one get Webview to display those (Linkify, probably) links with underlines etc? It's easy enough in a TextView since one can get the spans from a TextView and style them, but Webview doesn't expose any way to retrieve that data... at least not that I can see looking through the docs.
Here is some JS code which can be injected to linkify phone numbers, emails and urls:
function linkify() {
linkifyTexts(linkifyPhoneNumbers);
linkifyTexts(linkifyEmails);
linkifyTexts(linkifyWebAddresses1);
linkifyTexts(linkifyWebAddresses2);
}
function linkifyPhoneNumbers(text) {
text = text.replace(/\b\+?[0-9\-]+\*?\b/g, '$&');
return text;
}
function linkifyEmails(text) {
text = text.replace(/(\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim, '$1');
return text;
}
function linkifyWebAddresses1(text) {
text = text.replace(/(\b(https?|ftp):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/gim, '$1');
return text;
}
function linkifyWebAddresses2(text) {
text = text.replace(/(^|[^\/])(www\.[\S]+(\b|$))/gim, '$1$2');
return text;
}
var linkifyTexts = function(replaceFunc)
{
var tNodes = [];
getTextNodes(document.body,false,tNodes,false);
var l = tNodes.length;
while(l--)
{
wrapNode(tNodes[l], replaceFunc);
}
}
function getTextNodes(node, includeWhitespaceNodes,textNodes,match) {
if (node.nodeType == 3) {
if (includeWhitespaceNodes || !/^\s*$/.test(node.nodeValue)) {
if(match){
if(match.test(node.nodeValue))
textNodes.push(node);
}
else {
textNodes.push(node);
}
}
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
var subnode = node.childNodes[i];
if (subnode.nodeName != "A") {
getTextNodes(subnode,includeWhitespaceNodes,textNodes,match);
}
}
}
}
function wrapNode(n, replaceFunc) {
var temp = document.createElement('div');
if(n.data)
temp.innerHTML = replaceFunc(n.data);
else{
//whatever
}
while (temp.firstChild) {
n.parentNode.insertBefore(temp.firstChild,n);
}
n.parentNode.removeChild(n);
}
Given this:
http://code.google.com/p/android/issues/detail?id=742
it still doesn't seem to be a way to do this from Java directly. One thing that might work is to write some JavaScript code and run it after page is loaded, e.g. as given here:
In Android Webview, am I able to modify a webpage's DOM?
Here's an example of a similar thing:
Disabling links in android WebView
where the idea is to disable links. You may be able to use a similar approach to add some CSS, including underlining. A couple of other SOqs / links that might help:
Android: Injecting Javascript into a Webview outside the onPageFinished Event
Android: Injecting Javascript into a Webview outside the onPageFinished Event
http://iphoneincubator.com/blog/windows-views/how-to-inject-javascript-functions-into-a-uiwebview
Injecting Javascript into a Webview outside the onPageFinished Event (Using DatePicker to set a date on an input of a WebView)
Hope this helps.