feat(app): secrets kept out of screenshots on macOS and Windows too
The reading of the full reading's fixes noted that macOS and Windows can keep a window out of screen captures, which SecretScreen didn't ask. PlatformScreenGuard now asks the app's window on Android, macOS and Windows (asksOn), and both apps' runners answer app.halkyon/screen_guard, counting what shows: macOS's MainFlutterWindow sets sharingType = .none, Windows' FlutterWindow WDA_EXCLUDEFROMCAPTURE (Windows 10 2004 on; before, WDA_MONITOR, black in captures). Neither runner was built here (Linux): to be tried on each system. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
28fb6c0309
commit
b9bf9813c5
9 changed files with 218 additions and 19 deletions
|
|
@ -2,6 +2,13 @@ import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
class MainFlutterWindow: NSWindow {
|
class MainFlutterWindow: NSWindow {
|
||||||
|
/// The channel halkyon_app's SecretScreen asks to keep the window out of
|
||||||
|
/// screenshots and screen recordings while something secret shows (the
|
||||||
|
/// recovery phrase, recovery codes, an authenticator's key), and how many
|
||||||
|
/// show: counted, as several may at once.
|
||||||
|
private var screenGuard: FlutterMethodChannel?
|
||||||
|
private var secretsShown = 0
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
let flutterViewController = FlutterViewController()
|
let flutterViewController = FlutterViewController()
|
||||||
let windowFrame = self.frame
|
let windowFrame = self.frame
|
||||||
|
|
@ -9,7 +16,34 @@ class MainFlutterWindow: NSWindow {
|
||||||
self.setFrame(windowFrame, display: true)
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
guardScreen(messenger: flutterViewController.engine.binaryMessenger)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func guardScreen(messenger: FlutterBinaryMessenger) {
|
||||||
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "app.halkyon/screen_guard", binaryMessenger: messenger)
|
||||||
|
channel.setMethodCallHandler { [weak self] call, result in
|
||||||
|
guard let self = self else {
|
||||||
|
result(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch call.method {
|
||||||
|
case "hold":
|
||||||
|
self.secretsShown += 1
|
||||||
|
self.sharingType = .none
|
||||||
|
result(nil)
|
||||||
|
case "release":
|
||||||
|
self.secretsShown = max(0, self.secretsShown - 1)
|
||||||
|
if self.secretsShown == 0 {
|
||||||
|
self.sharingType = .readOnly
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenGuard = channel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
|
|
||||||
|
#include <flutter/standard_method_codec.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
// Kept out of captures altogether (Windows 10 2004 and later); older SDKs
|
||||||
|
// don't name it.
|
||||||
|
#ifndef WDA_EXCLUDEFROMCAPTURE
|
||||||
|
#define WDA_EXCLUDEFROMCAPTURE 0x00000011
|
||||||
|
#endif
|
||||||
|
|
||||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||||
: project_(project) {}
|
: project_(project) {}
|
||||||
|
|
||||||
|
|
@ -25,6 +33,7 @@ bool FlutterWindow::OnCreate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegisterPlugins(flutter_controller_->engine());
|
RegisterPlugins(flutter_controller_->engine());
|
||||||
|
GuardScreen();
|
||||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
|
|
@ -39,7 +48,38 @@ bool FlutterWindow::OnCreate() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlutterWindow::GuardScreen() {
|
||||||
|
screen_guard_ =
|
||||||
|
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||||
|
flutter_controller_->engine()->messenger(),
|
||||||
|
"app.halkyon/screen_guard",
|
||||||
|
&flutter::StandardMethodCodec::GetInstance());
|
||||||
|
screen_guard_->SetMethodCallHandler(
|
||||||
|
[this](const flutter::MethodCall<flutter::EncodableValue>& call,
|
||||||
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
||||||
|
result) {
|
||||||
|
if (call.method_name() == "hold") {
|
||||||
|
secrets_shown_++;
|
||||||
|
// Before Windows 10 2004 the exclusion fails: the window shows
|
||||||
|
// black in captures instead.
|
||||||
|
if (!SetWindowDisplayAffinity(GetHandle(), WDA_EXCLUDEFROMCAPTURE)) {
|
||||||
|
SetWindowDisplayAffinity(GetHandle(), WDA_MONITOR);
|
||||||
|
}
|
||||||
|
result->Success();
|
||||||
|
} else if (call.method_name() == "release") {
|
||||||
|
secrets_shown_ = secrets_shown_ > 0 ? secrets_shown_ - 1 : 0;
|
||||||
|
if (secrets_shown_ == 0) {
|
||||||
|
SetWindowDisplayAffinity(GetHandle(), WDA_NONE);
|
||||||
|
}
|
||||||
|
result->Success();
|
||||||
|
} else {
|
||||||
|
result->NotImplemented();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void FlutterWindow::OnDestroy() {
|
void FlutterWindow::OnDestroy() {
|
||||||
|
screen_guard_ = nullptr;
|
||||||
if (flutter_controller_) {
|
if (flutter_controller_) {
|
||||||
flutter_controller_ = nullptr;
|
flutter_controller_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
#define RUNNER_FLUTTER_WINDOW_H_
|
#define RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
||||||
#include <flutter/dart_project.h>
|
#include <flutter/dart_project.h>
|
||||||
|
#include <flutter/encodable_value.h>
|
||||||
#include <flutter/flutter_view_controller.h>
|
#include <flutter/flutter_view_controller.h>
|
||||||
|
#include <flutter/method_channel.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
@ -28,6 +30,17 @@ class FlutterWindow : public Win32Window {
|
||||||
|
|
||||||
// The Flutter instance hosted by this window.
|
// The Flutter instance hosted by this window.
|
||||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||||
|
|
||||||
|
// The channel halkyon_app's SecretScreen asks to keep the window out of
|
||||||
|
// screen captures while something secret shows (the recovery phrase,
|
||||||
|
// recovery codes, an authenticator's key), and how many show: counted, as
|
||||||
|
// several may at once.
|
||||||
|
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>
|
||||||
|
screen_guard_;
|
||||||
|
int secrets_shown_ = 0;
|
||||||
|
|
||||||
|
// Answers |screen_guard_|.
|
||||||
|
void GuardScreen();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
#endif // RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
class MainFlutterWindow: NSWindow {
|
class MainFlutterWindow: NSWindow {
|
||||||
|
/// The channel halkyon_app's SecretScreen asks to keep the window out of
|
||||||
|
/// screenshots and screen recordings while something secret shows (the
|
||||||
|
/// recovery phrase, recovery codes, an authenticator's key), and how many
|
||||||
|
/// show: counted, as several may at once.
|
||||||
|
private var screenGuard: FlutterMethodChannel?
|
||||||
|
private var secretsShown = 0
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
let flutterViewController = FlutterViewController()
|
let flutterViewController = FlutterViewController()
|
||||||
let windowFrame = self.frame
|
let windowFrame = self.frame
|
||||||
|
|
@ -9,7 +16,34 @@ class MainFlutterWindow: NSWindow {
|
||||||
self.setFrame(windowFrame, display: true)
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
guardScreen(messenger: flutterViewController.engine.binaryMessenger)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func guardScreen(messenger: FlutterBinaryMessenger) {
|
||||||
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "app.halkyon/screen_guard", binaryMessenger: messenger)
|
||||||
|
channel.setMethodCallHandler { [weak self] call, result in
|
||||||
|
guard let self = self else {
|
||||||
|
result(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch call.method {
|
||||||
|
case "hold":
|
||||||
|
self.secretsShown += 1
|
||||||
|
self.sharingType = .none
|
||||||
|
result(nil)
|
||||||
|
case "release":
|
||||||
|
self.secretsShown = max(0, self.secretsShown - 1)
|
||||||
|
if self.secretsShown == 0 {
|
||||||
|
self.sharingType = .readOnly
|
||||||
|
}
|
||||||
|
result(nil)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenGuard = channel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,17 @@
|
||||||
#include "flutter_window.h"
|
#include "flutter_window.h"
|
||||||
|
|
||||||
|
#include <flutter/standard_method_codec.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "flutter/generated_plugin_registrant.h"
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
// Kept out of captures altogether (Windows 10 2004 and later); older SDKs
|
||||||
|
// don't name it.
|
||||||
|
#ifndef WDA_EXCLUDEFROMCAPTURE
|
||||||
|
#define WDA_EXCLUDEFROMCAPTURE 0x00000011
|
||||||
|
#endif
|
||||||
|
|
||||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||||
: project_(project) {}
|
: project_(project) {}
|
||||||
|
|
||||||
|
|
@ -25,6 +33,7 @@ bool FlutterWindow::OnCreate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
RegisterPlugins(flutter_controller_->engine());
|
RegisterPlugins(flutter_controller_->engine());
|
||||||
|
GuardScreen();
|
||||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||||
|
|
||||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||||
|
|
@ -39,7 +48,38 @@ bool FlutterWindow::OnCreate() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlutterWindow::GuardScreen() {
|
||||||
|
screen_guard_ =
|
||||||
|
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
|
||||||
|
flutter_controller_->engine()->messenger(),
|
||||||
|
"app.halkyon/screen_guard",
|
||||||
|
&flutter::StandardMethodCodec::GetInstance());
|
||||||
|
screen_guard_->SetMethodCallHandler(
|
||||||
|
[this](const flutter::MethodCall<flutter::EncodableValue>& call,
|
||||||
|
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
|
||||||
|
result) {
|
||||||
|
if (call.method_name() == "hold") {
|
||||||
|
secrets_shown_++;
|
||||||
|
// Before Windows 10 2004 the exclusion fails: the window shows
|
||||||
|
// black in captures instead.
|
||||||
|
if (!SetWindowDisplayAffinity(GetHandle(), WDA_EXCLUDEFROMCAPTURE)) {
|
||||||
|
SetWindowDisplayAffinity(GetHandle(), WDA_MONITOR);
|
||||||
|
}
|
||||||
|
result->Success();
|
||||||
|
} else if (call.method_name() == "release") {
|
||||||
|
secrets_shown_ = secrets_shown_ > 0 ? secrets_shown_ - 1 : 0;
|
||||||
|
if (secrets_shown_ == 0) {
|
||||||
|
SetWindowDisplayAffinity(GetHandle(), WDA_NONE);
|
||||||
|
}
|
||||||
|
result->Success();
|
||||||
|
} else {
|
||||||
|
result->NotImplemented();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void FlutterWindow::OnDestroy() {
|
void FlutterWindow::OnDestroy() {
|
||||||
|
screen_guard_ = nullptr;
|
||||||
if (flutter_controller_) {
|
if (flutter_controller_) {
|
||||||
flutter_controller_ = nullptr;
|
flutter_controller_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@
|
||||||
#define RUNNER_FLUTTER_WINDOW_H_
|
#define RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
||||||
#include <flutter/dart_project.h>
|
#include <flutter/dart_project.h>
|
||||||
|
#include <flutter/encodable_value.h>
|
||||||
#include <flutter/flutter_view_controller.h>
|
#include <flutter/flutter_view_controller.h>
|
||||||
|
#include <flutter/method_channel.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
@ -28,6 +30,17 @@ class FlutterWindow : public Win32Window {
|
||||||
|
|
||||||
// The Flutter instance hosted by this window.
|
// The Flutter instance hosted by this window.
|
||||||
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
|
||||||
|
|
||||||
|
// The channel halkyon_app's SecretScreen asks to keep the window out of
|
||||||
|
// screen captures while something secret shows (the recovery phrase,
|
||||||
|
// recovery codes, an authenticator's key), and how many show: counted, as
|
||||||
|
// several may at once.
|
||||||
|
std::unique_ptr<flutter::MethodChannel<flutter::EncodableValue>>
|
||||||
|
screen_guard_;
|
||||||
|
int secrets_shown_ = 0;
|
||||||
|
|
||||||
|
// Answers |screen_guard_|.
|
||||||
|
void GuardScreen();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RUNNER_FLUTTER_WINDOW_H_
|
#endif // RUNNER_FLUTTER_WINDOW_H_
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,13 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
||||||
through Flutter, so clipboard histories and cloud clipboards would keep
|
through Flutter, so clipboard histories and cloud clipboards would keep
|
||||||
it): the recovery phrase and codes are saved or written down, an
|
it): the recovery phrase and codes are saved or written down, an
|
||||||
authenticator's key scanned, opened or typed; all of them show in a
|
authenticator's key scanned, opened or typed; all of them show in a
|
||||||
`SecretScreen`, kept out of screenshots and recents on Android
|
`SecretScreen` (the phrase typed back too), kept out of screenshots,
|
||||||
(`FLAG_SECURE`: the app's `MainActivity` answers `app.halkyon/screen_guard`,
|
screen recordings and recents where the app's window answers
|
||||||
as Learn's and Todo's `ScreenGuard.kt` do) and covered on iOS while the app
|
`app.halkyon/screen_guard`: Android's `FLAG_SECURE` (as Learn's and Todo's
|
||||||
isn't in front; keyboards learn nothing typed into a password, a code or
|
`ScreenGuard.kt` in their `MainActivity`), macOS's `sharingType = .none`
|
||||||
|
(`MainFlutterWindow.swift`) and Windows' `WDA_EXCLUDEFROMCAPTURE`
|
||||||
|
(`flutter_window.cpp`); covered on iOS while the app isn't in front;
|
||||||
|
nothing on Linux; keyboards learn nothing typed into a password, a code or
|
||||||
the phrase; no state's text (which a debug build's bloc observer logs) holds
|
the phrase; no state's text (which a debug build's bloc observer logs) holds
|
||||||
a secret, a code or the address; only an `otpauth:` URI is shown as a QR
|
a secret, a code or the address; only an `otpauth:` URI is shown as a QR
|
||||||
code or opened; and anything a step didn't expect (bytes of the wrong
|
code or opened; and anything a step didn't expect (bytes of the wrong
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:material_ui/material_ui.dart';
|
import 'package:material_ui/material_ui.dart';
|
||||||
|
|
||||||
|
|
@ -15,16 +16,29 @@ abstract interface class ScreenGuard() {
|
||||||
Future<void> release();
|
Future<void> release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [ScreenGuard] through Android's `FLAG_SECURE`, which each app's
|
/// [ScreenGuard] through the app's window, which each app's runner keeps
|
||||||
/// `MainActivity` sets while any is held (`ScreenGuard.kt`, counting them,
|
/// out while any is held, counting them, as several may show at once:
|
||||||
/// as several may show at once); nothing to do elsewhere: iOS has no such
|
/// Android's `FLAG_SECURE` (`ScreenGuard.kt` in its `MainActivity`), macOS's
|
||||||
/// flag (its app switcher's picture is covered by [SecretScreen] instead),
|
/// `sharingType = .none` (`MainFlutterWindow.swift`) and Windows'
|
||||||
/// and desktops none either.
|
/// `WDA_EXCLUDEFROMCAPTURE` (`flutter_window.cpp`; from Windows 10 2004 on).
|
||||||
class const PlatformScreenGuard({final bool? android}) implements ScreenGuard {
|
/// Nothing to ask elsewhere: iOS has no such flag (its app switcher's
|
||||||
/// The channel each app's activity answers.
|
/// picture is covered by [SecretScreen] instead), and Linux's desktops
|
||||||
|
/// none either.
|
||||||
|
class const PlatformScreenGuard({final bool? asks}) implements ScreenGuard {
|
||||||
|
/// The channel each app's window answers.
|
||||||
static const channel = MethodChannel('app.halkyon/screen_guard');
|
static const channel = MethodChannel('app.halkyon/screen_guard');
|
||||||
|
|
||||||
bool get _android => android ?? Platform.isAndroid;
|
/// Whether the app's window on [platform] answers [channel].
|
||||||
|
static bool asksOn(TargetPlatform platform) => switch (platform) {
|
||||||
|
TargetPlatform.android ||
|
||||||
|
TargetPlatform.macOS ||
|
||||||
|
TargetPlatform.windows => true,
|
||||||
|
TargetPlatform.iOS ||
|
||||||
|
TargetPlatform.linux ||
|
||||||
|
TargetPlatform.fuchsia => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool get _asks => asks ?? asksOn(defaultTargetPlatform);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> hold() => _ask('hold');
|
Future<void> hold() => _ask('hold');
|
||||||
|
|
@ -33,7 +47,7 @@ class const PlatformScreenGuard({final bool? android}) implements ScreenGuard {
|
||||||
Future<void> release() => _ask('release');
|
Future<void> release() => _ask('release');
|
||||||
|
|
||||||
Future<void> _ask(String method) async {
|
Future<void> _ask(String method) async {
|
||||||
if (!_android) {
|
if (!_asks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,9 @@ void main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("the platform's guard asks Android's activity, counted", (
|
testWidgets("the platform's guard asks the app's window, counted: "
|
||||||
tester,
|
"Android's activity, and macOS's and Windows' windows (the reading of "
|
||||||
) async {
|
'the fixes)', (tester) async {
|
||||||
final calls = <String>[];
|
final calls = <String>[];
|
||||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
PlatformScreenGuard.channel,
|
PlatformScreenGuard.channel,
|
||||||
|
|
@ -108,14 +108,22 @@ void main() {
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const guard = PlatformScreenGuard(android: true);
|
const guard = PlatformScreenGuard(asks: true);
|
||||||
await guard.hold();
|
await guard.hold();
|
||||||
await guard.release();
|
await guard.release();
|
||||||
expect(calls, ['hold', 'release']);
|
expect(calls, ['hold', 'release']);
|
||||||
|
|
||||||
// Elsewhere, nothing to ask.
|
// Elsewhere (iOS, Linux), nothing to ask.
|
||||||
calls.clear();
|
calls.clear();
|
||||||
await const PlatformScreenGuard(android: false).hold();
|
await const PlatformScreenGuard(asks: false).hold();
|
||||||
expect(calls, isEmpty);
|
expect(calls, isEmpty);
|
||||||
|
expect(
|
||||||
|
PlatformScreenGuard.asksOn(TargetPlatform.macOS),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
expect(PlatformScreenGuard.asksOn(TargetPlatform.windows), isTrue);
|
||||||
|
expect(PlatformScreenGuard.asksOn(TargetPlatform.android), isTrue);
|
||||||
|
expect(PlatformScreenGuard.asksOn(TargetPlatform.iOS), isFalse);
|
||||||
|
expect(PlatformScreenGuard.asksOn(TargetPlatform.linux), isFalse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue