using System; using System.Collections.Generic; using Xamarin.Forms; using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading.Tasks; using System.Linq; using System.ComponentModel; using System.Text; using Xamarin.Forms.CustomAttributes; using Xamarin.Forms.Internals; namespace Xamarin.Forms.Controls { #if APP [Preserve (AllMembers = true)] [Issue (IssueTracker.Github, 2357, "Webview waits to load the content until webviews on previous pages are loaded", PlatformAffected.iOS | PlatformAffected.Android)] public partial class Issue2357 : MasterDetailPage { public Issue2357 () { MasterViewModel = new MasterViewModel (); MasterViewModel.PageSelectionChanged += MasterViewModelOnPageSelectionChanged; BindingContext = MasterViewModel; Detail = new NavigationPage (new ContentPage { Title = "Home", Content = new Label { Text = "Hello, Forms !", VerticalOptions = LayoutOptions.CenterAndExpand, HorizontalOptions = LayoutOptions.CenterAndExpand } }); InitializeComponent (); } public MasterViewModel MasterViewModel { get; set; } async void MasterViewModelOnPageSelectionChanged (object sender, NavigationEventArgs eventArgs) { Debug.WriteLine ("MasterViewModelOnPageSelectionChanged"); IsPresented = false; var page = eventArgs.Page; await Detail.Navigation.PushAsync (page, true); } protected override async void OnAppearing () { await TryInitializeMasterViewModel (); base.OnAppearing (); } protected override void OnDisappearing () { //MasterViewModel.PageSelectionChanged -= MasterViewModelOnPageSelectionChanged; base.OnDisappearing (); } async Task TryInitializeMasterViewModel () { while (true) { string errorMessage; try { await MasterViewModel.InitializeAsync (); break; } catch (Exception ex) { Insights.Report (ex, Insights.Severity.Error); errorMessage = ex.Message; } if (!string.IsNullOrWhiteSpace (errorMessage)) { var retry = await DisplayAlert ("Error", errorMessage, "Retry", "Close Application"); if (retry) { continue; } } break; } } protected void ListViewOnItemTapped (object sender, ItemTappedEventArgs e) { Debug.WriteLine ("ListViewOnItemTapped"); if (((ListView)sender).SelectedItem == null) return; var menuItem = e.Item as MainMenuItem; if (menuItem != null) { switch (menuItem.MenuType) { case MenuType.Login: { break; } case MenuType.WebView: { var webViewViewModel = new WebViewViewModel (menuItem); MasterViewModel.CurrentDetailPage = new CustomWebView (webViewViewModel); break; } default: { //MenuType Standard break; } } ((ListView)sender).SelectedItem = null; } } } internal class CustomWebView : ContentPage { WebView _titledWebView; public CustomWebView () { _titledWebView = new WebView (); _titledWebView.SetBinding (WebView.SourceProperty, new Binding ("Url")); _titledWebView.Navigating += WebView_OnNavigating; this.SetBinding (TitleProperty, "Title"); Content = _titledWebView; } public CustomWebView (WebViewViewModel webViewViewModel) : this () { Debug.WriteLine ("New WebView"); _titledWebView.BindingContext = webViewViewModel; } static void WebView_OnNavigating (object sender, WebNavigatingEventArgs e) { Debug.WriteLine ("OS: " + Device.OS + " Current Url: " + GetSourceUrl (((WebView)sender).Source) + "Destination Url: " + e.Url + " " + DateTime.Now); if (e.Url.IsValidAbsoluteUrl ()) { var destinationUri = new Uri (e.Url); var sourceUri = GetSourceUrl (((WebView)sender).Source); if (sourceUri.HasSameHost (destinationUri)) { if (destinationUri == sourceUri) { //Do nothing. This happens on webview load Debug.WriteLine ("WebView_OnNavigating Same URI"); return; } //If it reaches here, A link could have been clicked. e.Cancel = true; Debug.WriteLine ("WebView_OnNavigating Same Host but different Uri"); } else { //if external link is clicked Debug.WriteLine ("WebView_OnNavigating, DIfferent Uri, so open in Native Browser"); e.Cancel = true; Device.OpenUri (new Uri (e.Url)); } } } static Uri GetSourceUrl (WebViewSource source) { Debug.Assert (source != null, "source cannot be null."); var urlWebViewSource = source as UrlWebViewSource; if (urlWebViewSource != null) { if (urlWebViewSource.Url.IsValidAbsoluteUrl ()) { return new Uri (urlWebViewSource.Url); } } throw new InvalidOperationException ("WebViewSource is Invalid. Only UrlWebViewSource is accepted."); } } public static class UriExtensions { public static bool HasSameHost (this Uri sourceUri, Uri destinationUri, UriFormat uriFormat = UriFormat.Unescaped) { Debug.Assert (sourceUri != null, "sourceUri cannot be null."); Debug.Assert (destinationUri != null, "destinationUri cannot be null."); return destinationUri.GetComponents (UriComponents.Host, uriFormat) == sourceUri.GetComponents (UriComponents.Host, uriFormat); } } public static class StringExtensions { public static bool IsValidAbsoluteUrl (this string stringValue) { Uri result; return !string.IsNullOrWhiteSpace (stringValue) && Uri.TryCreate (stringValue, UriKind.Absolute, out result) && (result.Scheme == "http" || result.Scheme == "https"); } } public delegate void PageSelectionChanged (object sender, NavigationEventArgs e); public class MasterViewModel : ViewModelBase1 { public static event PageSelectionChanged PageSelectionChanged; ObservableCollection _mainMenuItems; Page _currentDetailPage; public MasterViewModel () { _mainMenuItems = new ObservableCollection (Enumerable.Empty ()); } #pragma warning disable 1998 // considered for removal public async Task InitializeAsync () #pragma warning restore 1998 { var items = new List (); items.Add (new MainMenuItem { Title = "SHORT", MenuType = MenuType.WebView, Uri = new Uri ("http://api.morgans.bluearc-uat.com/mobile/SamplePage.aspx?page=Portfolio") }); items.Add (new MainMenuItem { Title = "LONG", MenuType = MenuType.WebView, Uri = new Uri ("http://api.morgans.bluearc-uat.com/mobile/SamplePage.aspx?page=long") }); MainMenuItems = new ObservableCollection (items); } public ObservableCollection MainMenuItems { get { return _mainMenuItems; } set { _mainMenuItems = value; OnPropertyChanged ("MainMenuItems"); } } public Page CurrentDetailPage { get { return _currentDetailPage; } set { _currentDetailPage = value; var handler = PageSelectionChanged; if (handler != null) { handler (null, new NavigationEventArgs (value)); } } } } public class WebViewViewModel : ViewModelBase1 { string _title; string _url; public WebViewViewModel (MainMenuItem menuItem) { Debug.WriteLine ("New WebViewViewModel"); _title = menuItem.Title; _url = menuItem.Uri.AbsoluteUri; } public string Title { get { return _title; } set { _title = value; OnPropertyChanged ("Title"); } } public string Url { get { return _url; } set { Debug.WriteLine ("WebViewViewModel Url Changed"); _url = value; OnPropertyChanged ("Url"); } } } public interface IMenuService { Task> GetMenuItemsAsync (); } public class MainMenuItem { public object Id { get; set; } public MenuType MenuType { get; set; } public string Title { get; set; } public Uri Uri { get; set; } } public enum MenuType { Login, WebView, Standard } public class ViewModelBase1 : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; /// /// Event for when IsBusy changes /// public event EventHandler IsBusyChanged; /// /// Event for when IsValid changes /// public event EventHandler IsValidChanged; readonly List _errors = new List (); bool _isBusy; /// /// Default constructor /// public ViewModelBase1 () { //Make sure validation is performed on startup Validate (); } /// /// Returns true if the current state of the ViewModel is valid /// public bool IsValid { get { return _errors.Count == 0; } } /// /// A list of errors if IsValid is false /// protected List Errors { get { return _errors; } } /// /// An aggregated error message /// public string Error { get { return _errors.Aggregate (new StringBuilder (), (b, s) => b.AppendLine (s)).ToString ().Trim (); } } /// /// Protected method for validating the ViewModel /// - Fires PropertyChanged for IsValid and Errors /// protected void Validate () { OnPropertyChanged ("IsValid"); OnPropertyChanged ("Errors"); var method = IsValidChanged; if (method != null) method (this, EventArgs.Empty); } /// /// Other viewmodels should call this when overriding Validate, to validate each property /// /// Func to determine if a value is valid /// The error message to use if not valid protected void ValidateProperty (Func validate, string error) { if (validate ()) { if (!Errors.Contains (error)) Errors.Add (error); } else { Errors.Remove (error); } } /// /// Value indicating if a spinner should be shown /// public bool IsBusy { get { return _isBusy; } set { if (_isBusy != value) { _isBusy = value; OnPropertyChanged ("IsBusy"); OnIsBusyChanged (); } } } /// /// Other viewmodels can override this if something should be done when busy /// protected void OnIsBusyChanged () { var ev = IsBusyChanged; if (ev != null) { ev (this, EventArgs.Empty); } } protected void OnPropertyChanged (string name) { var ev = PropertyChanged; if (ev != null) { ev (this, new PropertyChangedEventArgs (name)); } } } #endif }