diff --git a/qController.Standard/Views/ControlPage.xaml.cs b/qController.Standard/Views/ControlPage.xaml.cs new file mode 100644 index 0000000..9e34a50 --- /dev/null +++ b/qController.Standard/Views/ControlPage.xaml.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using Xamarin.Forms; +using Serilog; +using Acr.UserDialogs; + +using qController.Dialogs; +using qController.QItems; +using qController.Cell; +using qController.Communication; +using qController.Events; + +namespace qController +{ + public partial class ControlPage : ContentPage + { + QController qController; + QSelectedCueCell qCell; + QLevelsCell qLevelsCell; + QCueListCell qCueListCell; + QLevelsButton showLevelsButton; + QControlsBlock qControlsBlock; + WorkspacePrompt workspacePrompt = new WorkspacePrompt(); + + public ControlPage(string name, string address) + { + InitializeComponent(); + + qController = new QController(address, 53000); + qController.qClient.qParser.WorkspaceInfoReceived += WorkspaceInfoReceived; + qController.qClient.qParser.WorkspaceUpdated += WorkspaceUpdated; + qController.qClient.qParser.WorkspaceDisconnect += WorkspaceDisconnected; + qController.qClient.qParser.PlaybackPositionUpdated += PlaybackPositionUpdated; + qController.qClient.qParser.ConnectionStatusChanged += OnConnectionStatusChanged; + qController.qClient.qParser.CueInfoUpdated += OnCueUpdateReceived; + qController.qClient.qParser.ChildrenUpdated += OnChildrenUpdated; + workspacePrompt.WorkspaceSelected += OnWorkspaceSelected; + + App.rootPage.MenuItemSelected += OnMenuItemSelected; + + instanceName.Text = name; + + InitGUI(); + + if (!qController.qClient.connected) + { + App.showToast("Error connecting...make sure QLab is running"); + Back(); + } + else + { + qController.KickOff(); + } + } + + private void OnWorkspaceSelected(object source, WorkspacePromptArgs args) + { + if (args.SelectedWorkspace != null) + { + qController.Connect(args.SelectedWorkspace); + } + else + { + Log.Debug("CONTROLPAGE - No Workspace selected backing out"); + Back(); + } + } + + private void OnConnectionStatusChanged(object source, ConnectEventArgs args) + { + Log.Debug($"CONTROLPAGE - Connection Status Changed: {args.WorkspaceId} : {args.Status}"); + if (args.Status.Equals("ok")) + { + Device.BeginInvokeOnMainThread(() => { + FinishUI(); + }); + } + else if (args.Status.Equals("badpass")) + { + workspacePrompt.promptWorkspacePasscode(args.WorkspaceId); + } + } + + private void WorkspaceInfoReceived(object source, WorkspaceInfoArgs args) + { + if (args.WorkspaceInfo.Count > 1) + { + Log.Debug("CONTROLPAGE - MULTIPLE WORKSPACES ON SELECTED COMPUTER"); + PromptForWorkspace(args.WorkspaceInfo); + } + else if (args.WorkspaceInfo.Count == 1) + { + Log.Debug("CONTROLPAGE - ONLY ONE WORKSPACE ON SELECTED COMPUTER"); + if (!args.WorkspaceInfo[0].hasPasscode) + { + qController.Connect(args.WorkspaceInfo[0].uniqueID); + + } + else + { + workspacePrompt.promptWorkspacePasscode(args.WorkspaceInfo[0].uniqueID); + } + + } + else + { + NoWorkspacesConfig noWorkspacesConfig = new NoWorkspacesConfig(NoWorkspaceDetected); + UserDialogs.Instance.Confirm(noWorkspacesConfig); + } + } + + private void PromptForWorkspace(List workspaces) + { + + UserDialogs.Instance.ActionSheet(workspacePrompt.getActionSheetConfigForWorkspaces(workspaces)); + } + + private void InitGUI() + { + App.rootPage.MenuPage.ChangeToControl(); + NavigationPage.SetHasNavigationBar(this, false); + + qCell = new QSelectedCueCell(); + + qCell.SelectedCueEdited += OnSelectedCueEdited; + + AbsoluteLayout.SetLayoutFlags(qCell, AbsoluteLayoutFlags.All); + + instanceName.FontSize = App.HeightUnit * 3; + + switch (Device.RuntimePlatform) + { + case Device.iOS: + AbsoluteLayout.SetLayoutBounds(qCell, new Rectangle(0, 0.13, 1, 0.30)); + topBar.HeightRequest = App.Height * .09; + menuButton.FontSize = App.Height * .04; + break; + case Device.Android: + AbsoluteLayout.SetLayoutBounds(qCell, new Rectangle(0, 0.13, 1, 0.35)); + topBar.HeightRequest = App.Height * .06; + menuButton.FontSize = App.Height * .05; + break; + } + + //Menu Button Setup + var menuButtonGesture = new TapGestureRecognizer(); + + menuButtonGesture.Tapped += ShowMenu; + menuButton.GestureRecognizers.Add(menuButtonGesture); + menuButton.Margin = new Thickness(App.WidthUnit * 2, 0, 0, App.WidthUnit * 2); + + sLayout.Children.Add(qCell); + } + + private void SendOSCFromButton(object sender, EventArgs args) + { + if (((QButton)sender).qCommand.type == "WORKSPACE") + { + string workspace_prefix = "/workspace/" + qController.qWorkspace.workspace_id; + string command = workspace_prefix + ((QButton)sender).qCommand.osc; + qController.qClient.sendTCP(command); + } + } + + private void OnSelectedCueEdited(object source, CueEditArgs args) + { + string address = "/workspace/" + qController.qWorkspace.workspace_id + "/cue_id/" + args.CueID + "/" + args.Property; + qController.qClient.sendTCP(address, args.NewValue); + } + + void FinishUI() + { + string workspace_prefix = "/workspace/" + qController.qWorkspace.workspace_id; + + qLevelsCell = new QLevelsCell(); + + qLevelsCell.mainSlider.ValueChanged += (sender, args) => + { + qController.qClient.sendTCP(workspace_prefix + "/cue_id/" + qLevelsCell.activeCue + "/sliderLevel/0", (float)args.NewValue); + }; + qLevelsCell.leftSlider.ValueChanged += (sender, args) => + { + qController.qClient.sendTCP(workspace_prefix + "/cue_id/" + qLevelsCell.activeCue + "/sliderLevel/1", (float)args.NewValue); + }; + qLevelsCell.rightSlider.ValueChanged += (sender, args) => + { + qController.qClient.sendTCP(workspace_prefix + "/cue_id/" + qLevelsCell.activeCue + "/sliderLevel/2", (float)args.NewValue); + }; + + + showLevelsButton = new QLevelsButton(); + showLevelsButton.button.Clicked += ToggeQLevelsCellVisiblity; + + qControlsBlock = new QControlsBlock(SendOSCFromButton); + + AbsoluteLayout.SetLayoutFlags(qControlsBlock, AbsoluteLayoutFlags.All); + AbsoluteLayout.SetLayoutFlags(qLevelsCell, AbsoluteLayoutFlags.All); + AbsoluteLayout.SetLayoutFlags(showLevelsButton, AbsoluteLayoutFlags.PositionProportional); + + + switch (Device.RuntimePlatform) + { + case Device.iOS: + AbsoluteLayout.SetLayoutBounds(qControlsBlock, new Rectangle(0, 0.53, 1, 0.25)); + AbsoluteLayout.SetLayoutBounds(qLevelsCell, new Rectangle(0.9, 0.40, 0.8, 0.25)); + AbsoluteLayout.SetLayoutBounds(showLevelsButton, new Rectangle(0.02, 0.34, App.HeightUnit * 8, App.HeightUnit * 8)); + break; + case Device.Android: + AbsoluteLayout.SetLayoutBounds(qControlsBlock, new Rectangle(0, 0.58, 1, 0.25)); + AbsoluteLayout.SetLayoutBounds(qLevelsCell, new Rectangle(0.9, 0.45, 0.8, 0.25)); + AbsoluteLayout.SetLayoutBounds(showLevelsButton, new Rectangle(0.02, 0.39, App.HeightUnit * 8, App.HeightUnit * 8)); + break; + } + + sLayout.Children.Add(qControlsBlock); + sLayout.Children.Add(qLevelsCell); + sLayout.Children.Add(showLevelsButton); + } + + private void ToggeQLevelsCellVisiblity(object sender, EventArgs e) + { + qLevelsCell.IsVisible = !qLevelsCell.IsVisible; + } + + void ShowMenu(object sender, EventArgs e) + { + + App.MenuIsPresented = true; + } + + void Back() + { + if(qController.qClient.connected) + qController.Kill(); + App.rootPage.MenuItemSelected -= OnMenuItemSelected; + Device.BeginInvokeOnMainThread(() => + { + App.rootPage.MenuPage.ChangeToHome(); + App.NavigationPage.Navigation.PopAsync(); + }); + } + + public void NoWorkspaceDetected(bool resp) + { + if (resp) + Back(); + } + public void WorkspaceDisconnected(object sender, EventArgs e) + { + Back(); + } + + public void WorkspaceUpdated(object sender, WorkspaceEventArgs e) + { + if(e.UpdatedWorkspace.data.Count > 0) + { + qController.qWorkspace = e.UpdatedWorkspace; + qController.qWorkspace.CheckPopulated(); + + if (qController.qWorkspace.IsPopulated) + { + Device.BeginInvokeOnMainThread(() => + { + App.rootPage.MenuPage.ChangeToWorkspace(e.UpdatedWorkspace); + }); + if (qCell.activeCue == null) + { + Device.BeginInvokeOnMainThread(() => { + qCell.UpdateSelectedCue(new NoQCueSelected()); + }); + Log.Debug("CONTROLPAGE - Update Selected Cue Called because of Inital Workspace Load"); + qController.qClient.UpdateSelectedCue(qController.qWorkspace.workspace_id); + } + Log.Debug("CONTROLPAGE - Workspace Updated " + qController.qWorkspace.workspace_id); + } + } + } + + public void PlaybackPositionUpdated(object sender, PlaybackPositionArgs e) + { + qController.playbackPosition = e.PlaybackPosition; + Log.Debug("CONTROLPAGE - Playback Position Updated " + qController.playbackPosition); + QCue cue = qController.qWorkspace.GetCue(qController.playbackPosition); + if(cue != null) + { + Device.BeginInvokeOnMainThread(() => { + if (cue.levels != null) + showLevelsButton.IsVisible = true; + else + { + showLevelsButton.IsVisible = false; + qLevelsCell.IsVisible = false; + } + qCell.UpdateSelectedCue(cue); + }); + } + } + + public void OnCueUpdateReceived(object sender, CueEventArgs args) + { + if(qController != null) + { + if(qController.qWorkspace != null) + { + qController.qWorkspace.UpdateCue(args.Cue); + if (qController.playbackPosition == null) + { + qController.playbackPosition = args.Cue.uniqueID; + } + + if (args.Cue.uniqueID == qController.playbackPosition) + { + Device.BeginInvokeOnMainThread(() => + { + Log.Debug("CONTROLPAGE - Refreshing Currently Displayed Cue"); + if (args.Cue.levels != null) + showLevelsButton.IsVisible = true; + else + { + showLevelsButton.IsVisible = false; + qLevelsCell.IsVisible = false; + } + qCell.UpdateSelectedCue(args.Cue); + if(qLevelsCell != null) + { + qLevelsCell.activeCue = args.Cue.uniqueID; + if (args.Cue.levels != null) + { + qLevelsCell.UpdateLevels(args.Cue.levels[0]); + } + } + }); + } + } + } + + } + + private void OnChildrenUpdated(object source, ChildrenEventArgs args) + { + if(qController != null) + { + if(qController.qWorkspace != null) + { + qController.qWorkspace.UpdateChildren(args.cue_id, args.children); + } + } + } + + private void OnMenuItemSelected(object source, MenuEventArgs args) + { + if (args.Command.Contains("/")) + { + qController.qClient.sendTCP(args.Command); + } + else if (args.Command == "disconnect") + { + Back(); + }else if (args.Command.Contains("cueList")) + { + var parts = args.Command.Split(' '); + if(parts.Length > 1) + { + Device.BeginInvokeOnMainThread(() => + { + qCueListCell = new QCueListCell(qController.qWorkspace.GetCueList(parts[1])); + qCueListCell.closeButton.Clicked += CloseCueList; + qCueListCell.cueListView.ItemSelected += OnCueListItemSelected; + AbsoluteLayout.SetLayoutFlags(qCueListCell, AbsoluteLayoutFlags.All); + AbsoluteLayout.SetLayoutBounds(qCueListCell, new Rectangle(0, 0.2, 1, 0.9)); + sLayout.Children.Add(qCueListCell); + }); + } + } + } + + private void CloseCueList(object sender, EventArgs e) + { + Device.BeginInvokeOnMainThread(() => + { + sLayout.Children.Remove(qCueListCell); + }); + } + + private void OnCueListItemSelected(object sender, SelectedItemChangedEventArgs e) + { + OSCListItem cue = (OSCListItem)e.SelectedItem; + string selectCueOSC = "/workspace/" + qController.qWorkspace.workspace_id + cue.Command; + qController.qClient.sendTCP(selectCueOSC); + CloseCueList(sender,e); + } + + } +} diff --git a/qController.Standard/Views/HomePage.xaml.cs b/qController.Standard/Views/HomePage.xaml.cs new file mode 100644 index 0000000..39c8f5a --- /dev/null +++ b/qController.Standard/Views/HomePage.xaml.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +using Xamarin.Forms; + +namespace qController +{ + public partial class HomePage : ContentPage + { + public HomePage() + { + InitializeComponent(); + } + } +} diff --git a/qController.Standard/Views/MenuPage.xaml.cs b/qController.Standard/Views/MenuPage.xaml.cs new file mode 100644 index 0000000..0bb254c --- /dev/null +++ b/qController.Standard/Views/MenuPage.xaml.cs @@ -0,0 +1,135 @@ +using System.Collections.ObjectModel; +using Xamarin.Forms; +using qController.QItems; +using System; + +namespace qController +{ + public partial class MenuPage : ContentPage + { + + public ObservableCollection items { get; } = new ObservableCollection(); + public MenuPage() + { + Title = "Menu"; + InitializeComponent(); + + ChangeToHome(); + + listView.ItemsSource = items; + listView.RowHeight = (int)(App.HeightUnit * 6); + listView.ItemTemplate = new DataTemplate(() => + { + var grid = new Grid { + Padding = new Thickness(5, 10), + ColumnDefinitions = + { + new ColumnDefinition{Width = new GridLength(30)}, + new ColumnDefinition { Width = GridLength.Star } + } + }; + + + var icon = new Label { + FontFamily = App.QFont, + HorizontalTextAlignment = TextAlignment.Center, + VerticalTextAlignment = TextAlignment.Center, + FontSize = App.HeightUnit * 3 + }; + + + icon.SetBinding(Label.TextProperty, "Icon"); + icon.SetDynamicResource(Label.TextColorProperty, "IconTextColor"); + + var label = new Label { + VerticalOptions = LayoutOptions.FillAndExpand + }; + label.SetBinding(Label.TextProperty, "Title"); + label.SetDynamicResource(Label.TextColorProperty, "PrimaryTextColor"); + + if (label.Text == "Disconnect") + label.TextColor = Color.DarkRed; + + + switch (Device.RuntimePlatform) + { + case Device.iOS: + label.FontSize = App.HeightUnit * 3; + break; + case Device.Android: + label.FontSize = App.HeightUnit * 2.2; + break; + } + + + grid.Children.Add(icon); + grid.Children.Add(label, 1, 0); + return new ViewCell { View = grid }; + }); + + } + + public void setItemSelected(EventHandler ItemSelected){ + listView.ItemSelected += ItemSelected; + } + + public void clearSelectedItem() + { + listView.SelectedItem = null; + } + + public void ChangeToControl() + { + items.Clear(); + items.Add(new MenuPageItem + { + Title = "Disconnect", + Icon = QIcon.CANCEL, + Command = "disconnect" + }); + + } + public void ChangeToWorkspace(QOldWorkspace workspace) + { + for(int i = 0; i < workspace.data.Count; i++) + { + var cueList = workspace.data[i]; + items.Add(new MenuPageItem + { + Title = cueList.listName, + Icon = cueList.IconText, + Command = "cueList " + cueList.uniqueID + }); + } + } + + public void ChangeToHome() + { + items.Clear(); + items.Add(new MenuPageItem + { + Title = "Scan Network", + Icon = "\uE800", + Command = "scan" + }); + items.Add(new MenuPageItem + { + Title = "Add Manually", + Icon = "\uE801", + Command = "add" + }); + items.Add(new MenuPageItem + { + Title = "Send Feedback", + Icon = QIcon.MAIL, + Command = "feedback" + }); + items.Add(new MenuPageItem + { + Title = "Support Project", + Icon = QIcon.DOLLAR, + Command = "support" + }); + } + } +} diff --git a/qController.Standard/Views/QBrowserPage.xaml.cs b/qController.Standard/Views/QBrowserPage.xaml.cs new file mode 100644 index 0000000..dae85b0 --- /dev/null +++ b/qController.Standard/Views/QBrowserPage.xaml.cs @@ -0,0 +1,103 @@ +using QControlKit; +using Xamarin.Forms; +using Serilog; +using qController.Cell; +using Acr.UserDialogs; +using qController.Dialogs; +using Xamarin.Essentials; +using System; +using qController.ViewModels; + +namespace qController.Pages +{ + public partial class QBrowserPage : ContentPage + { + public QBrowserPage() + { + InitializeComponent(); + + SetupTopBar(); + + serverListView.BindingContext = new QBrowserViewModel(new QBrowser()); + serverListView.ItemSelected += QWorkspaceSelected; + + storageListView.ItemsSource = QStorage.qInstances; + storageListView.ItemTemplate = new DataTemplate(typeof(QInstanceCell)); + storageListView.ItemTapped += (object sender, ItemTappedEventArgs e) => + { + // don't do anything if we just de-selected the row + if (e.Item == null) return; + // do something with e.SelectedItem + ((ListView)sender).SelectedItem = null; // de-select the row + }; + + } + + private void SetupTopBar() + { + NavigationPage.SetHasNavigationBar(this, false); + App.rootPage.MenuItemSelected += OnMenuItemSelected; + } + + async void QWorkspaceSelected(object sender, SelectedItemChangedEventArgs e) + { + if (e.SelectedItem == null) + return; + QWorkspace selectedWorkspace = (e.SelectedItem as QWorkspaceViewModel).workspace; + Log.Debug($"[demo] workspace: {selectedWorkspace.nameWithoutPathExtension} has been selected"); + ((ListView)sender).SelectedItem = null; + + if (selectedWorkspace.hasPasscode) + { + UserDialogs.Instance.Prompt(new PromptConfig + { + InputType = InputType.Number, + MaxLength = 4, + Title = "Workspace Requires Passcode", + OkText = "Connect", + IsCancellable = true, + OnTextChanged = args => + { + args.IsValid = args.Value != null && !args.Value.Equals("") && args.Value.Length == 4; + }, + OnAction = async (resp) => + { + if (resp.Ok) + { + Console.WriteLine($"Passcode is null: {resp.Value == null}"); + await Navigation.PushAsync(new WorkspacePage(selectedWorkspace, resp.Value)); + } + } + + }); + } + else + { + await Navigation.PushAsync(new WorkspacePage(selectedWorkspace)); + } + } + + void AddWorkspace() + { + UserDialogs.Instance.Prompt(new AddInstancePrompt()); + } + + private void OnMenuItemSelected(object source, MenuEventArgs args) + { + if (args.Command == "add") + { + AddWorkspace(); + } + else if (args.Command == "feedback") + { + Launcher.OpenAsync(new Uri("mailto:feedback@jwetzell.com?subject=qController%20feedback")); + } + else if (args.Command == "support") + { + UserDialogs.Instance.Confirm(new DonatePrompt()); + } + } + } + + +} \ No newline at end of file diff --git a/qController.Standard/Views/RootPage.xaml.cs b/qController.Standard/Views/RootPage.xaml.cs new file mode 100644 index 0000000..e914898 --- /dev/null +++ b/qController.Standard/Views/RootPage.xaml.cs @@ -0,0 +1,46 @@ +using System; +using Xamarin.Forms; + +namespace qController +{ + public class MenuEventArgs : EventArgs + { + public string Command + { + get; + set; + } + } + + public partial class RootPage : MasterDetailPage + { + public MenuPage MenuPage; + public delegate void MenuItemSelectedHandler(object source, MenuEventArgs args); + public event MenuItemSelectedHandler MenuItemSelected; + + public RootPage() + { + InitializeComponent(); + + } + + public void Init() + { + MenuPage = (MenuPage)Master; + + MenuPage.setItemSelected(OnItemSelected); + } + + void OnItemSelected(object sender, SelectedItemChangedEventArgs e) + { + var item = e.SelectedItem as MenuPageItem; + if (item != null) + { + MenuPage.clearSelectedItem(); + IsPresented = false; + if (MenuItemSelected != null) + MenuItemSelected(this, new MenuEventArgs { Command = item.Command }); + } + } + } +} diff --git a/qController.Standard/Views/RootPageDetail.xaml.cs b/qController.Standard/Views/RootPageDetail.xaml.cs new file mode 100644 index 0000000..141c122 --- /dev/null +++ b/qController.Standard/Views/RootPageDetail.xaml.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace qController.Pages +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class RootPageDetail : ContentPage + { + public RootPageDetail() + { + InitializeComponent(); + } + } +} diff --git a/qController.Standard/Views/RootPageMaster.xaml.cs b/qController.Standard/Views/RootPageMaster.xaml.cs new file mode 100644 index 0000000..e378e9a --- /dev/null +++ b/qController.Standard/Views/RootPageMaster.xaml.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace qController.Pages +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class RootPageMaster : ContentPage + { + public ListView ListView; + + public RootPageMaster() + { + InitializeComponent(); + + BindingContext = new RootPageMasterViewModel(); + ListView = MenuItemsListView; + } + + class RootPageMasterViewModel : INotifyPropertyChanged + { + public ObservableCollection MenuItems { get; set; } + + public RootPageMasterViewModel() + { + MenuItems = new ObservableCollection(new[] + { + new RootPageMenuItem { Id = 0, Title = "Page 1" }, + new RootPageMenuItem { Id = 1, Title = "Page 2" }, + new RootPageMenuItem { Id = 2, Title = "Page 3" }, + new RootPageMenuItem { Id = 3, Title = "Page 4" }, + new RootPageMenuItem { Id = 4, Title = "Page 5" }, + }); + } + + #region INotifyPropertyChanged Implementation + public event PropertyChangedEventHandler PropertyChanged; + void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + if (PropertyChanged == null) + return; + + PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + } + } +} diff --git a/qController.Standard/Views/WorkspacePage.xaml.cs b/qController.Standard/Views/WorkspacePage.xaml.cs new file mode 100644 index 0000000..e60c463 --- /dev/null +++ b/qController.Standard/Views/WorkspacePage.xaml.cs @@ -0,0 +1,169 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Serilog; +using Xamarin.Essentials; +using Xamarin.Forms; +using Acr.UserDialogs; + +using qController.ViewModels; +using qController.UI; + +using QControlKit; +using QControlKit.Events; +using QControlKit.Constants; + +namespace qController +{ + public partial class WorkspacePage : ContentPage + { + private QWorkspace connectedWorkspace; + private Dictionary cueGridDict = new Dictionary(); + public WorkspacePage(QWorkspace workspace, string passcode = null) + { + InitializeComponent(); + + + connectedWorkspace = workspace; + + SetupTopBar(); + + connectedWorkspace.WorkspaceUpdated += Workspace_WorkspaceUpdated; + connectedWorkspace.WorkspaceConnectionError += ConnectedWorkspace_WorkspaceConnectionError; + connectedWorkspace.defaultSendUpdatesOSC = true; + connectedWorkspace.CueListChangedPlaybackPosition += ConnectedWorkspace_CueListChangedPlaybackPosition; + + connectedWorkspace.connect(passcode); + + + } + private void SetupTopBar() + { + NavigationPage.SetHasNavigationBar(this, false); + + App.rootPage.MenuPage.ChangeToControl(); + App.rootPage.MenuItemSelected += OnMenuItemSelected; + + qNavBar.instanceName.Text = connectedWorkspace.serverName; + + } + + private void ConnectedWorkspace_WorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs connectionErrorArgs) + { + System.Console.WriteLine("WorkspaceConnectionError Called"); + if (connectionErrorArgs.status.Equals("badpass")) + { + UserDialogs.Instance.Prompt(new PromptConfig + { + InputType = InputType.Number, + MaxLength = 4, + Title = "Incorrect Passcode!", + OkText = "Connect", + IsCancellable = true, + OnTextChanged = args => + { + args.IsValid = args.Value != null && !args.Value.Equals("") && args.Value.Length == 4; + }, + OnAction = (resp) => + { + if (resp.Ok) + { + connectedWorkspace.connect(resp.Value); + } + } + }); + } + } + + void ConnectedWorkspace_CueListChangedPlaybackPosition(object source, QCueListChangedPlaybackPositionArgs args) + { + Device.BeginInvokeOnMainThread(() => + { + QCue selectedCue = connectedWorkspace.cueWithID(args.cueID); + connectedWorkspace.fetchDefaultPropertiesForCue(selectedCue); + selectedCueFrame.BindingContext = new QCueViewModel(selectedCue, false); + if (cueGridDict.ContainsKey(args.cueID)) + { + var cueGrid = cueGridDict[args.cueID]; //element to scroll to + cueListScrollView.ScrollToAsync(cueGrid, ScrollToPosition.Center, true); + } + }); + } + + void Workspace_WorkspaceUpdated(object source, QWorkspaceUpdatedArgs args) + { + Log.Debug("[workspacepage] Workspace Updated"); + + foreach (var cue in connectedWorkspace.cueLists) + { + if (cue.cues.Count > 0) + { + List cueAddTasks = new List(); + foreach (var aCue in cue.cues) + { + QCueGrid cueGrid = cueToGrid(aCue); + cueGridDict.Add(aCue.uid, cueGrid); + MainThread.InvokeOnMainThreadAsync(() => + { + cueListsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + cueListsGrid.Children.Add(cueGrid, 0, aCue.sortIndex); + }).Wait(); + } + connectedWorkspace.valueForKey(cue, QOSCKey.PlaybackPositionId); //fetch playback position for cueList once all cue loading is done. + break; //only load first cue list with cues. + } + } + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + + } + + QCueGrid cueToGrid(QCue cue) + { + QCueGrid cueGrid = new QCueGrid(cue); + + if (cue.cues.Count > 0) + { + foreach (var aCue in cue.cues) + { + cueGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + var aCueGrid = cueToGrid(aCue); + cueGridDict.Add(aCue.uid, aCueGrid); + aCueGrid.Margin = new Thickness(10, 0, 0, 0); + cueGrid.Children.Add(aCueGrid, 0, aCue.sortIndex + 1); + } + } + return cueGrid; + } + + void Back() + { + + if (connectedWorkspace.connected) + connectedWorkspace.disconnect(); //TODO: This might not be implemented? + + //unsubscribe from events + App.rootPage.MenuItemSelected -= OnMenuItemSelected; + connectedWorkspace.WorkspaceUpdated -= Workspace_WorkspaceUpdated; + connectedWorkspace.CueListChangedPlaybackPosition -= ConnectedWorkspace_CueListChangedPlaybackPosition; + connectedWorkspace.WorkspaceConnectionError -= ConnectedWorkspace_WorkspaceConnectionError; + + Device.BeginInvokeOnMainThread(() => + { + App.rootPage.MenuPage.ChangeToHome(); + App.NavigationPage.Navigation.PopAsync(); + }); + } + + private void OnMenuItemSelected(object source, MenuEventArgs args) + { + + if (args.Command == "disconnect") + { + Back(); + } + } + } +} \ No newline at end of file diff --git a/qController.Standard/Views/qConnectionPage.xaml.cs b/qController.Standard/Views/qConnectionPage.xaml.cs new file mode 100644 index 0000000..ab34d28 --- /dev/null +++ b/qController.Standard/Views/qConnectionPage.xaml.cs @@ -0,0 +1,114 @@ +using Xamarin.Forms; +using System; +using Acr.UserDialogs; +using Zeroconf; +using System.Collections.Generic; +using Serilog; +using qController.QItems; +using qController.Dialogs; +using qController.Cell; +using Xamarin.Essentials; + +namespace qController +{ + + public partial class QConnectionPage : ContentPage + { + public QConnectionPage() + { + InitializeComponent(); + InitGUI(); + App.rootPage.MenuItemSelected += OnMenuItemSelected; + } + + private void OnMenuItemSelected(object source, MenuEventArgs args) + { + if(args.Command == "scan") + { + Scan(); + } + else if(args.Command == "add") + { + AddWorkspace(); + } + else if (args.Command == "feedback") + { + Launcher.OpenAsync(new Uri("mailto:feedback@jwetzell.com?subject=qController%20feedback")); + } + else if(args.Command == "support") + { + UserDialogs.Instance.Confirm(new DonatePrompt()); + } + } + + private void InitGUI() + { + NavigationPage.SetHasNavigationBar(this, false); + + //ListView Setup + listView.ItemsSource = QStorage.qInstances; + listView.ItemTemplate = new DataTemplate(typeof(QInstanceCell)); + listView.ItemTapped += (object sender, ItemTappedEventArgs e) => + { + // don't do anything if we just de-selected the row + if (e.Item == null) return; + // do something with e.SelectedItem + ((ListView)sender).SelectedItem = null; // de-select the row + }; + + //Platform Specific Setup + switch (Device.RuntimePlatform) + { + case Device.iOS: + topBar.HeightRequest = App.Height * .09; + menuButton.FontSize = App.Height * .04; + menuButton.Margin = new Thickness(App.WidthUnit * 2, 0, 0, App.WidthUnit * 2); + break; + case Device.Android: + topBar.HeightRequest = App.Height * .09; + menuButton.FontSize = App.Height * .05; + menuButton.Margin = new Thickness(App.WidthUnit * 2, 0, 0, App.WidthUnit * 2); + break; + } + + BackgroundColor = Color.FromHex("4A4A4A"); + + //MenuButton Setup + var menuButtonGesture = new TapGestureRecognizer(); + menuButtonGesture.Tapped += App.ShowMenu; + menuButton.GestureRecognizers.Add(menuButtonGesture); + + } + + void AddWorkspace(){ + UserDialogs.Instance.Prompt(new AddInstancePrompt()); + } + + async void Scan(){ + bool workspacesFound = false; + App.showToast("Scanning for Instances..."); + Log.Debug("QCONNECTIONPAGE - Begin Scanning"); + + IReadOnlyList results = await ZeroconfResolver.ResolveAsync("_qlab._udp.local.",TimeSpan.FromSeconds(3)); + if(results != null){ + foreach (var result in results) + { + if (result != null) + { + QInstance instance = new QInstance(result.DisplayName, result.IPAddress); + if(QStorage.AddInstance(instance)){ + Log.Debug($"QCONNECTIONPAGE - {result.DisplayName} @ {result.IPAddress} added"); + workspacesFound = true; + } + } + } + } + + if(workspacesFound){ + App.showToast("Instance Found and Added!"); + }else{ + App.showToast("No New Instances Found!"); + } + } + } +}