using UIKit; namespace VoiceCat.iOS; internal static class UiHelpers { internal static void ShowError(UIViewController owner, Exception exception) => ShowMessage(owner, "VoiceCat", exception.Message); internal static void ShowMessage(UIViewController owner, string title, string message) { UIAlertController alert = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert); alert.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); owner.PresentViewController(alert, true, null); } internal static UITextField Field(string placeholder, bool secure = false) { var field = new UITextField { Placeholder = placeholder, BorderStyle = UITextBorderStyle.RoundedRect, SecureTextEntry = secure, TranslatesAutoresizingMaskIntoConstraints = false }; field.AccessibilityLabel = placeholder; return field; } } // VoiceOver rebuilds its element tree from scratch on every UITableView.ReloadData. The model // notifies far more often than any list's content actually changes - the microphone level timer // alone ticks at 20 Hz - so reloading unconditionally re-announces the row under an exploring // finger and destroys the element a double tap was aimed at. Reload only when the rendered text // actually differs, and only while the view is on screen, so an off-screen or popped controller // stops reloading instead of holding a subscription for the lifetime of the connection. internal sealed class ListRefresher(UITableViewController owner, Func signature, Action subscribe, Action unsubscribe) { // Null means nothing has been rendered yet. A signature is never null, so the first // Refresh after Start or Invalidate always reloads. private string? rendered; private bool observing; internal void Start() { if (observing) return; observing = true; subscribe(Refresh); Refresh(); } internal void Stop() { if (!observing) return; observing = false; unsubscribe(Refresh); } // Forces the next Refresh to reload even when the signature is unchanged, for state the // signature cannot see. internal void Invalidate() => rendered = null; internal void Refresh() { if (!observing || !owner.IsViewLoaded) return; string next = signature(); if (next == rendered) return; rendered = next; owner.TableView.ReloadData(); } }