public void Start(Book.Book book, CollectionSettings collectionSettings, Color backColor) { if (_wifiAdvertiser != null) { Stop(); } // This listens for a BloomReader to request a book. // It requires a firewall hole allowing Bloom to receive messages on _portToListen. // We initialize it before starting the Advertiser to avoid any chance of a race condition // where a BloomReader manages to request an advertised book before we start the listener. _wifiListener = new BloomReaderUDPListener(); _wifiListener.NewMessageReceived += (sender, args) => { var json = Encoding.UTF8.GetString(args.Data); try { dynamic settings = JsonConvert.DeserializeObject(json); // The property names used here must match the ones in BloomReader, doInBackground method of SendMessage, // a private class of NewBookListenerService. var androidIpAddress = (string)settings.deviceAddress; var androidName = (string)settings.deviceName; // This prevents the device (or other devices) from queuing up requests while we're busy with this one. // In effect, the Android is only allowed to request a retry after we've given up this try at sending. // Of course, there are async effects from network latency. But if we do get another request while // handling this one, we will ignore it, since StartSendBook checks for a transfer in progress. _wifiAdvertiser.Paused = true; StartSendBookOverWiFi(book, androidIpAddress, androidName, backColor); // Returns immediately. But we don't resume advertisements until the async send completes. } // If there's something wrong with the JSON (maybe an obsolete or newer version of reader?) // just ignore the request. catch (Exception ex) when(ex is JsonReaderException || ex is JsonSerializationException) { _progress.Error(id: "BadBookRequest", message: "Got a book request we could not process. Possibly the device is running an incompatible version of BloomReader?"); //this is too technical/hard to translate _progress.ErrorWithoutLocalizing($" Request contains {json}; trying to interpret as JSON we got {ex.Message}"); } }; var pathHtmlFile = book.GetPathHtmlFile(); _wifiAdvertiser = new WiFiAdvertiser(_progress) { BookTitle = BookStorage.SanitizeNameForFileSystem(book.Title), // must be the exact same name as the file we will send if requested TitleLanguage = collectionSettings.Language1Iso639Code, BookVersion = Book.Book.MakeVersionCode(File.ReadAllText(pathHtmlFile), pathHtmlFile) }; AndroidView.CheckBookLayout(book, _progress); _wifiAdvertiser.Start(); _progress.Message(id: "WifiInstructions1", message: "On the Android, run Bloom Reader, open the menu and choose 'Receive Books from computer'."); _progress.Message(id: "WifiInstructions2", message: "You can do this on as many devices as you like. Make sure each device is connected to the same network as this computer."); }
public static void Save(Book.Book book, BookServer bookServer, Color backColor, WebSocketProgress progress) { var progressWithL10N = progress.WithL10NPrefix("PublishTab.Android.File.Progress."); using (var dlg = new DialogAdapters.SaveFileDialogAdapter()) { dlg.DefaultExt = BookCompressor.ExtensionForDeviceBloomBook; var bloomdFileDescription = LocalizationManager.GetString("PublishTab.Android.bloomdFileFormatLabel", "Bloom Book for Devices", "This is shown in the 'Save' dialog when you save a bloom book in the format that works with the Bloom Reader Android App"); dlg.Filter = $"{bloomdFileDescription}|*{BookCompressor.ExtensionForDeviceBloomBook}"; dlg.FileName = Path.GetFileName(book.FolderPath) + BookCompressor.ExtensionForDeviceBloomBook; if (!string.IsNullOrWhiteSpace(Settings.Default.BloomDeviceFileExportFolder) && Directory.Exists(Settings.Default.BloomDeviceFileExportFolder)) { dlg.InitialDirectory = Settings.Default.BloomDeviceFileExportFolder; //(otherwise leave to default save location) } if (DialogResult.OK == dlg.ShowDialog()) { Settings.Default.BloomDeviceFileExportFolder = Path.GetDirectoryName(dlg.FileName); AndroidView.CheckBookLayout(book, progress); PublishToAndroidApi.SendBook(book, bookServer, dlg.FileName, null, progressWithL10N, (publishedFileName, bookTitle) => progressWithL10N.GetMessageWithParams("Saving", "{0} is a file path", "Saving as {0}", dlg.FileName), null, backColor); PublishToAndroidApi.ReportAnalytics("file", book); } } }
protected override void LoadView(IShellContext shellContext) { base.LoadView(shellContext); var recycler = AndroidView.FindViewById <RecyclerView>(Resource.Id.flyoutcontent_recycler); recyclerAdapter = new AppShellFlyoutRecyclerAdapter(shellContext, OnElementSelected); recycler.SetAdapter(recyclerAdapter); }
protected override TView CreateItem(AndroidContext context, AndroidView <TView> element) { var view = (TView)Activator.CreateInstance(typeof(TView), context.Context); if (context.LayoutParamsFactory != null) { // hang on, we need to get the dimensions from the attributes var dimensions = element.Attributes.GetAttributeOrDefault(View.DimensionsAttribute); view.LayoutParameters = context.LayoutParamsFactory(dimensions ?? Dimensions.WrapWrap); } return(view); }
/// <summary> /// Attempt to connect to a device /// </summary> /// <param name="book"></param> public void Connect(Book.Book book, Color backColor) { try { // Calls to this come from JavaScript, not sure they will always be on the UI thread. // Before I added this, I definitely saw race conditions with more than one thread trying // to figure out what was connected. lock (this) { AndroidView.CheckBookLayout(book, _progress); if (_connectionHandler != null) { // we're in an odd state...should only be able to click the button that calls this // while stopped. // Try to really get into the right state in case the user tries again. _androidDeviceUsbConnection.StopFindingDevice(); return; } // Create this while locked...once we have it, can't enter the main logic of this method // on another thread. _connectionHandler = new BackgroundWorker(); } _progress.Message(id: "LookingForDevice", message: "Looking for an Android device connected by USB cable and set up for MTP...", comment: "This is a progress message; MTP is an acronym for the system that allows computers to access files on devices."); _androidDeviceUsbConnection.OneReadyDeviceFound = HandleFoundAReadyDevice; _androidDeviceUsbConnection.OneReadyDeviceNotFound = HandleFoundOneNonReadyDevice; // Don't suppress the first message after (re)starting. _previousDeviceNotFoundReportType = DeviceNotFoundReportType.Unknown; _connectionHandler.DoWork += (sender, args) => _androidDeviceUsbConnection.ConnectAndSendToOneDevice(book, backColor); _connectionHandler.RunWorkerCompleted += (sender, args) => { if (args.Error != null) { UsbFailConnect(args.Error); } _connectionHandler = null; // now OK to try to connect again. }; _connectionHandler.RunWorkerAsync(); } catch (Exception e) { UsbFailConnect(e); } }
/// <summary> /// Create a <see cref="AndroidView{TView}"/> instance without optional dimensions and id /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dimensions"></param> /// <param name="id"></param> /// <returns></returns> public static AndroidView <T> View <T>(Dimensions dimensions = default, int?id = default) where T : Android.Views.View { var control = new AndroidView <T>(); if (dimensions != default) { control.Dimensions(dimensions); } if (id.HasValue) { control.Id(id.Value); } return(control); }
public void SetDisplayMode(PublishModel.DisplayModes displayMode) { // This is only supposed to be active in one mode of PublishView. Browser.SuppressJavaScriptErrors = false; if (displayMode != PublishModel.DisplayModes.Upload && _publishControl != null) { Controls.Remove(_publishControl); _publishControl = null; } if (displayMode != PublishModel.DisplayModes.EPUB && _epubPreviewControl != null && Controls.Contains(_epubPreviewControl)) { Controls.Remove(_epubPreviewControl); } if (displayMode != PublishModel.DisplayModes.Android && _androidControl != null && Controls.Contains(_androidControl)) { Controls.Remove(_androidControl); // disposal of the browser is good but it hides a multitude of sins that we'd rather catch and fix during development. E.g. BL-4901 if (!ApplicationUpdateSupport.IsDevOrAlpha) { _androidControl.Dispose(); _androidControl = null; } } if (displayMode != PublishModel.DisplayModes.Upload && displayMode != PublishModel.DisplayModes.EPUB && displayMode != PublishModel.DisplayModes.Android) { _pdfViewer.Visible = true; } switch (displayMode) { case PublishModel.DisplayModes.WaitForUserToChooseSomething: _printButton.Enabled = _saveButton.Enabled = false; Cursor = Cursors.Default; _workingIndicator.Visible = false; _pdfViewer.Visible = false; break; case PublishModel.DisplayModes.Working: _printButton.Enabled = _saveButton.Enabled = false; _workingIndicator.Cursor = Cursors.WaitCursor; Cursor = Cursors.WaitCursor; _workingIndicator.Visible = true; _pdfViewer.Visible = false; break; case PublishModel.DisplayModes.ShowPdf: Logger.WriteEvent("Entering Publish PDF Screen"); if (RobustFile.Exists(_model.PdfFilePath)) { _pdfViewer.Visible = true; _workingIndicator.Visible = false; Cursor = Cursors.Default; _saveButton.Enabled = true; _printButton.Enabled = _pdfViewer.ShowPdf(_model.PdfFilePath); } break; case PublishModel.DisplayModes.Printing: _simpleAllPagesRadio.Enabled = false; _bookletCoverRadio.Enabled = false; _bookletBodyRadio.Enabled = false; _printButton.Enabled = _saveButton.Enabled = false; _workingIndicator.Cursor = Cursors.WaitCursor; Cursor = Cursors.WaitCursor; _workingIndicator.Visible = true; break; case PublishModel.DisplayModes.ResumeAfterPrint: _simpleAllPagesRadio.Enabled = true; _pdfViewer.Visible = true; _workingIndicator.Visible = false; Cursor = Cursors.Default; _saveButton.Enabled = true; _printButton.Enabled = true; break; case PublishModel.DisplayModes.Upload: { Logger.WriteEvent("Entering Publish Upload Screen"); _workingIndicator.Visible = false; // If we haven't finished creating the PDF, we will indicate that in the progress window. _saveButton.Enabled = _printButton.Enabled = false; // Can't print or save in this mode...wouldn't be obvious what would be saved. _pdfViewer.Visible = false; Cursor = Cursors.Default; if (_publishControl == null) { SetupPublishControl(); } break; } case PublishModel.DisplayModes.EPUB: { Logger.WriteEvent("Entering Publish Epub Screen"); // We may reuse this for the process of generating the ePUB staging files. For now, skip it. _workingIndicator.Visible = false; _printButton.Enabled = false; // don't know how to print an ePUB _pdfViewer.Visible = false; Cursor = Cursors.WaitCursor; _epubPreviewControl = ElectronicPublishView.SetupEpubControl(_epubPreviewControl, _isolator, () => _saveButton.Enabled = _model.EpubMaker.ReadyToSave()); _epubPreviewControl.SetBounds(_pdfViewer.Left, _pdfViewer.Top, _pdfViewer.Width, _pdfViewer.Height); _epubPreviewControl.Dock = _pdfViewer.Dock; _epubPreviewControl.Anchor = _pdfViewer.Anchor; var saveBackGround = _epubPreviewControl.BackColor; // changed to match parent during next statement Controls.Add(_epubPreviewControl); _epubPreviewControl.BackColor = saveBackGround; // keep own color. // Typically this control is dock.fill. It has to be in front of tableLayoutPanel1 (which is Left) for Fill to work. _epubPreviewControl.BringToFront(); Cursor = Cursors.Default; // We rather mangled the Readium code in the process of cutting away its own navigation // and other controls. It produces all kinds of JavaScript errors, but it seems to do // what we want. So just suppress the toasts for all of them. Browser.SuppressJavaScriptErrors = true; break; } case PublishModel.DisplayModes.Android: { Logger.WriteEvent("Entering Publish Android Screen"); _workingIndicator.Visible = false; _printButton.Enabled = false; _pdfViewer.Visible = false; Cursor = Cursors.WaitCursor; _androidControl = new AndroidView(_isolator); _androidControl.SetBounds(_pdfViewer.Left, _pdfViewer.Top, _pdfViewer.Width, _pdfViewer.Height); _androidControl.Dock = _pdfViewer.Dock; _androidControl.Anchor = _pdfViewer.Anchor; var saveBackGround = _androidControl.BackColor; // changed to match parent during next statement Controls.Add(_androidControl); _androidControl.BackColor = saveBackGround; // keep own color. // Typically this control is dock.fill. It has to be in front of tableLayoutPanel1 (which is Left) for Fill to work. _androidControl.BringToFront(); Cursor = Cursors.Default; break; } } UpdateSaveButton(); }