Files
voice-cat/clients/apple/VoiceCat.iOS/UiHelpers.cs
T

59 lines
2.4 KiB
C#
Raw Normal View History

2026-09-19 15:43:37 +02:00
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<string> signature, Action<Action> subscribe, Action<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();
}
}