Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ package's `README.md` and `PORTING_REPORT.md`.
| [`cloud_functions_tvos`](packages/cloud_functions_tvos) [![pub](https://img.shields.io/pub/v/cloud_functions_tvos.svg)](https://pub.dev/packages/cloud_functions_tvos) | [`cloud_functions`](https://pub.dev/packages/cloud_functions) |
| [`firebase_analytics_tvos`](packages/firebase_analytics_tvos) [![pub](https://img.shields.io/pub/v/firebase_analytics_tvos.svg)](https://pub.dev/packages/firebase_analytics_tvos) | [`firebase_analytics`](https://pub.dev/packages/firebase_analytics) |
| [`firebase_crashlytics_tvos`](packages/firebase_crashlytics_tvos) [![pub](https://img.shields.io/pub/v/firebase_crashlytics_tvos.svg)](https://pub.dev/packages/firebase_crashlytics_tvos) | [`firebase_crashlytics`](https://pub.dev/packages/firebase_crashlytics) |
| [`url_launcher_tvos`](packages/url_launcher_tvos) [![pub](https://img.shields.io/pub/v/url_launcher_tvos.svg)](https://pub.dev/packages/url_launcher_tvos) | [`url_launcher`](https://pub.dev/packages/url_launcher) |

### Evaluated but not provided

Expand All @@ -52,7 +53,6 @@ misleading:

| Plugin | Why not on tvOS |
|---|---|
| [`url_launcher`](https://pub.dev/packages/url_launcher) | No Safari / arbitrary URL or app launching on tvOS |
| [`google_sign_in`](https://pub.dev/packages/google_sign_in) | No GoogleSignIn tvOS SDK; tvOS uses a different device-pairing flow |
| [`geolocator`](https://pub.dev/packages/geolocator) | No location services on Apple TV |
| [`permission_handler`](https://pub.dev/packages/permission_handler) | tvOS lacks the permission surfaces (location, camera, photos, …) |
Expand Down
29 changes: 29 additions & 0 deletions packages/url_launcher_tvos/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Dart / Flutter
.dart_tool/
build/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub/

# CocoaPods
tvos/Pods/
tvos/Podfile.lock
tvos/.symlinks/
tvos/Flutter/Flutter.framework
tvos/Flutter/Flutter.podspec

# SwiftPM build cache (regenerated by `flutter-tvos build`)
.build/

# Xcode / SwiftPM (per-user, generated when tvos/Package.swift is opened)
**/.swiftpm/
**/xcuserdata/

# IDE
.idea/
.vscode/
*.iml

# macOS
.DS_Store
11 changes: 11 additions & 0 deletions packages/url_launcher_tvos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 0.0.1

* Initial tvOS implementation of `url_launcher`, ported from `url_launcher_ios`
6.4.1. External launches (`launchUrl`) and `canLaunchUrl` work via
`UIApplication.open` / `canOpenURL`. The in-app browser modes
(`inAppBrowserView` / `inAppWebView`) are unsupported on tvOS
(no SafariServices): `supportsMode` reports `false`, and a launch requested
with an in-app mode falls back to an external launch (the same fallback the
browser-less macOS/Windows/Linux implementations make) rather than throwing.
Unlike those three, `supportsMode` also reports `true` for
`externalNonBrowserApplication`, since `universalLinksOnly` is forwarded to the host.
25 changes: 25 additions & 0 deletions packages/url_launcher_tvos/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
112 changes: 112 additions & 0 deletions packages/url_launcher_tvos/PORTING_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# url_launcher_tvos — porting report

Ported by `flutter-tvos plugin port`, then finished + verified by hand.

Source: `url_launcher_ios` 6.4.1 (Swift, Pigeon 26). Base platform: ios.
Output: `./url_launcher_tvos`

## Summary

| Status | Count |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Pigeon methods kept + registered on tvOS | 4 (`canLaunchUrl`, `launchUrl`, `openUrlInSafariViewController`, `closeSafariViewController`) |
| Native methods behaving as-is on tvOS | 2 (`canLaunchUrl`, `launchUrl`) |
| Host methods kept for conformance, never called from tvOS Dart | 2 (`openUrlInSafariViewController`, `closeSafariViewController` — Dart falls back to external) |
| Native regions disabled on tvOS | 1 (the entire `URLLaunchSession` — SFSafariViewController) |
| tvOS build outlook | ✅ compiles (arm64 simulator, verified) |

## The one tvOS-absent API

`url_launcher_ios`'s only tvOS-incompatible surface is the **in-app browser**:
`SFSafariViewController` from **SafariServices**, which does not exist on tvOS.
Everything else — `UIApplication.canOpenURL` / `open(_:options:)` — is available
on tvOS. So `canLaunchUrl` and external `launchUrl` port unchanged; only the
in-app browser path is disabled.

## Native changes

- **`messages.g.swift` (generated Pigeon) — kept verbatim** except one line: the
import gate widened from `#if os(iOS)` to `#if os(iOS) || os(tvOS)`. The porter
had additionally wrapped `UrlLauncherApiSetup.setUp` in `#if !os(tvOS)` (it
matched the string `SFSafariViewController` inside a doc comment) — that would
have compiled out **all** channel registration on tvOS, so every method would
throw `MissingPluginException`. Reverted to upstream so all four channels
register.
- **`URLLaunchSession.swift`** — the whole class (`SFSafariViewControllerDelegate`
- `import SafariServices`) is wrapped in `#if !os(tvOS)`; it is never referenced
on tvOS.
- **`URLLauncherPlugin.swift`** — all four `UrlLauncherApi` methods are
implemented (protocol conformance intact). `canLaunchUrl` / `launchUrl` are
verbatim upstream. On tvOS, `openUrlInSafariViewController` returns
`InAppLoadResult.noUI` (there is no browser UI to present) and
`closeSafariViewController` is a no-op; the real Safari path stays under
`#if !os(tvOS)`.
- **`ViewPresenter.swift`, `Launcher.swift`** — verbatim upstream; both are pure
UIKit / `UIApplication` and compile on tvOS unchanged.

## Dart changes

This package's Dart runs **only on tvOS**, so it states tvOS behaviour directly
(no platform guards). The class is renamed `UrlLauncherIOS` → `UrlLauncherTvos`
(the `dartPluginClass` follows). The Pigeon-generated `messages.g.dart` is kept
**byte-identical to upstream 6.4.1**. The tvOS-honest behaviour:

- `supportsMode(inAppBrowserView` / `inAppWebView)` → `false` (was `true`);
`supportsCloseForMode(...)` → `false` (nothing to close).
- **`launchUrl` falls back to an external launch for _every_ mode** (external,
`platformDefault`, in-app) — the same fallback the browser-less
macOS/Windows/Linux impls make. The _supported set_ is not the same as theirs:
they report `supportsMode` `true` only for `platformDefault` and
`externalApplication`, whereas this package also reports `true` for
`externalNonBrowserApplication`, because it really does forward
`universalLinksOnly` to the host. The fallback keeps the deprecated
`launch('https://…')` — which infers an in-app mode from the URL scheme — from
throwing: it launches externally and an unclaimed URL returns `false`. The
in-app host methods stay registered for conformance but are unused on tvOS.
- The deprecated `launch()` override tests `universalLinksOnly` **before**
`useSafariVC`, the reverse of iOS. The shim infers
`useSafariVC = forceSafariVC ?? isWebURL`, so an iOS-shaped order leaves the
`universalLinksOnly` branch unreachable for web URLs and forwards the wrong
flag; iOS gets away with it because its SafariVC branch honours the request
in-app, and tvOS has no such branch.

## Packaging

- Podspec: no Flutter CocoaPod dependency (resolved via `FRAMEWORK_SEARCH_PATHS`);
`s.platform = :tvos, '13.0'` (mirrors upstream's iOS 13 floor); privacy manifest
shipped as a `resource_bundles` entry.
- Ships **both** a podspec (CocoaPods) and `tvos/Package.swift` (Swift Package
Manager — the Flutter 3.44 default), matching the repo's other pure-Swift
method-channel `_tvos` plugins (e.g. `shared_preferences_tvos`). flutter-tvos's
Podfile skips SPM-owned plugins, so the two never double-link.
- **Version floor:** the package keeps the repo-standard `flutter: >=3.13.0`, not
upstream 6.4.1's `>=3.38.0` / Dart 3.10 (raised in 6.4.0). Nothing in this tvOS
slice uses an API that needs the higher floor — the port builds and `dart
analyze`s clean under it — and `>=3.13.0` keeps it consistent with the sibling
`_tvos` packages.

## Verification

- `dart analyze` clean; `flutter test` — **11/11 pass**, including a `_FakeApi`
that asserts every mode (external, `platformDefault`, in-app, deprecated
`launch()`) reaches the external channel and never throws.
- **`flutter-tvos build tvos --simulator`** — Xcode build succeeds (arm64), via
both the CocoaPods and SPM paths.
- **Runtime on an Apple TV 4K simulator** — all four channels round-trip with no
`MissingPluginException`; `canLaunchUrl(web)` returns `true` while `launchUrl`
returns `false`, so `canLaunchUrl` isn't a reliable gate (documented in README).
- **Real cross-app launch, two ways** (throwaway targets, not shipped): on the
sim the example launched a second app via `targetapp://` (its `AppDelegate`
logged the delivered URL); on a **physical Apple TV 4K** (release/AOT,
`devicectl`) `launchUrl` opened the **App Store**. `UIApplication.open`
genuinely hands off on tvOS.

## Checklist

- [x] All Pigeon methods kept and registered on tvOS (no `MissingPluginException`).
- [x] tvOS-absent API (`SFSafariViewController`) disabled behind `#if !os(tvOS)`;
the two affected handlers return an honest result rather than crashing.
- [x] Generated files (`messages.g.dart`, `messages.g.swift`) match upstream
(the Swift file differs only by the import gate).
- [x] `flutter-tvos build tvos --simulator` compiles the example.
- [x] Version set (`0.0.1`) and `CHANGELOG.md` updated.
55 changes: 55 additions & 0 deletions packages/url_launcher_tvos/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# url_launcher_tvos

The tvOS implementation of [`url_launcher`](https://pub.dev/packages/url_launcher).

> Ported with [`flutter-tvos plugin port`](https://github.com/fluttertv/flutter-tvos)
> from `url_launcher_ios` 6.4.1, then finished + verified by hand. See
> `PORTING_REPORT.md`.

## Usage

Federated plugin implementation — no imports needed from app code; it registers
automatically. `url_launcher` does not endorse a tvOS implementation, so add this
package **explicitly** alongside it:

```yaml
dependencies:
url_launcher: ^6.3.2
url_launcher_tvos: ^0.0.1
```

Then use the `url_launcher` API as usual — but not every call behaves as it does
on iOS. There is no browser on tvOS, so the in-app browser modes fall back to an
external launch and `canLaunchUrl` can return `true` for a web URL nothing will
open. See the table below before porting an iOS app over.

## What works on tvOS — and what doesn't

tvOS has **no web browser** (no SafariServices / WebKit), so this
implementation supports only the _external_ launch surface:

| Capability | tvOS | Notes |
| ---------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `canLaunchUrl` | ✅ | maps to `UIApplication.canOpenURL` |
| `launchUrl` (external / universal link / app scheme) | ✅ | maps to `UIApplication.open`; opens another installed app |
| `launchUrl` in-app browser (`inAppBrowserView` / `inAppWebView`) | ⚠️ falls back | no `SFSafariViewController` on tvOS — `supportsMode` returns `false`, and a launch requested with an in-app mode **falls back to an external launch** (the same fallback macOS/Windows/Linux make) rather than throwing |
| `closeWebView` | ❌ (no-op) | nothing to close — there is no in-app browser |

Because there is no browser, a plain `http(s)` URL only opens if another
installed app claims it (universal link / app URL scheme). Note that on tvOS
`canLaunchUrl` can return `true` for a web URL even when nothing will actually
handle it, so rely on the boolean returned by `launchUrl` rather than gating on
`canLaunchUrl` alone. **Every** launch mode — `platformDefault`, and the in-app
browser modes — resolves to an **external** launch on tvOS (the iOS
implementation opens web URLs in-app instead).

## Status

| Platform | Implemented | Verified |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Apple TV simulator (`appletvsimulator`) | yes | ✅ builds (arm64); on a running Apple TV 4K sim all four channels round-trip (no `MissingPluginException`), **and a real external launch of a registered app URL scheme returns `true` and is delivered to the target** (`UIApplication.open` hands off) |
| Apple TV (`appletvos`) | yes | ✅ verified on a **physical Apple TV 4K** (release/AOT) — `launchUrl` opened the App Store (a real cross-app hand-off via `UIApplication.open`) |

## License

The FlutterTV Authors under a BSD-3-Clause license. See `LICENSE` for the full text.
7 changes: 7 additions & 0 deletions packages/url_launcher_tvos/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include: package:flutter_lints/flutter.yaml

analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
3 changes: 3 additions & 0 deletions packages/url_launcher_tvos/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# url_launcher_example

Demonstrates how to use the url_launcher plugin.
Loading
Loading