Files
ux/macos/Classes/UrlPlugin.swift
agra b4b5ee58a9 feat: UxUrl — native URL / phone / email detection + tap launcher
Sync FFI from Dart into platform detectors:
- iOS / macOS: NSDataDetector(.link | .phoneNumber) + a tight bare-domain
  pass that requires `/` or `?` (so `etc.` / `v1.2.3` don't false-positive
  while `example.com/path` does match). NFKD-fold the phone capture so
  full-width / Arabic-Indic digits collapse to ASCII; stop the digit run
  at the first letter so `+1 555 1234 ext.99` doesn't fuse the extension.
- Android: JNI into android.util.Patterns (WEB_URL / EMAIL_ADDRESS / PHONE)
  via a cached JavaVM, std::call_once for init, full per-call
  ExceptionCheck coverage. UTF-16→UTF-8 conversion is hand-rolled to dodge
  the Modified-UTF-8 / CESU-8 incompatibility with Dart's utf8.decode.

`UxUrl.launch(url)` is the matching tap action. Channel side dispatches via
UIApplication / NSWorkspace / Intent.ACTION_VIEW. Dart-side gates the URL
against a scheme allowlist (http, https, mailto, tel, sms, banlu, tg),
rejects bidi-override controls (U+202A..E / U+2066..9) to prevent visual
spoofs, and blocks USSD / MMI tel: codes containing `*` or `#`.

Library/native cleanup along the way:
- Renamed libux_keyboard.so to libux.so (also covers sensor + url).
- Collapsed three near-identical FFI loader stanzas across keyboard / sensor
  / url into a shared lib/src/_ffi.dart with `uxLib` + typed `uxLookupX`
  helpers.
2026-05-14 22:59:25 +03:00

32 lines
1.0 KiB
Swift

import FlutterMacOS
import AppKit
public class UrlPlugin: NSObject, NativePlugin {
private var channel: FlutterMethodChannel?
public func register(with registrar: FlutterPluginRegistrar) {
let c = FlutterMethodChannel(name: "ux/url", binaryMessenger: registrar.messenger)
c.setMethodCallHandler { [weak self] call, result in
self?.handle(call, result: result)
}
channel = c
}
private func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "launch": handleLaunch(call, result: result)
default: result(FlutterMethodNotImplemented)
}
}
private func handleLaunch(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let s = args["url"] as? String,
let url = URL(string: s) else {
return result(false)
}
let ok = NSWorkspace.shared.open(url)
result(ok)
}
}