Skip to content
Open
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
42 changes: 39 additions & 3 deletions lib/src/legacy/window_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import 'dart:async';
import 'dart:io';
import 'dart:ui' show Brightness, Color, Offset, PlatformDispatcher, Rect, Size;

import 'package:flutter/foundation.dart' show ObserverList, VoidCallback;
import 'package:flutter/foundation.dart'
show ObserverList, VoidCallback, visibleForTesting;
import 'package:flutter/painting.dart' show Alignment;
import 'package:nativeapi/nativeapi.dart' as nativeapi;
import 'package:nativeapi_flutter/nativeapi_flutter.dart'
Expand Down Expand Up @@ -66,6 +67,15 @@ class WindowManager {
nativeapi.Window? _window;
nativeapi.ListenerId? _nativeListenerId;

/// Addresses [window] instead of the platform's current window, or goes
/// back to the current window with null. For tests of this bridge only.
@visibleForTesting
void debugBindWindow(nativeapi.Window? window) {
_unwireNativeEvents();
_window = window;
if (_listeners.isNotEmpty || _isPreventClose) _wireNativeEvents();
}

bool _isPreventClose = false;
bool _isDestroying = false;
bool _isMaximized = false;
Expand Down Expand Up @@ -569,11 +579,33 @@ class WindowManager {
_target?.isVisibleInTaskbar = !isSkipTaskbar;
}

/// Replaces [nativeapi.Application.instance]. For tests of this bridge only.
@visibleForTesting
nativeapi.Application? debugApplicationOverride;

nativeapi.Application get _application =>
debugApplicationOverride ?? nativeapi.Application.instance;

/// Sets progress value in progress bar. Valid range is [0, 1.0].
///
/// @platforms macos,windows
Future<void> setProgressBar(double progress) async {
tryNative(() => nativeapi.Application.instance.setProgressBar(progress));
final window = _target;
tryNative(() {
_designatePrimaryWindow(window);
_application.setProgressBar(progress);
});
}

// The taskbar progress and badge belong to the application's primary
// window. Without one, Windows falls back to the first visible titled
// top-level window on the desktop, which need not be this app's. Designate
// the addressed window unless the app chose a primary window itself.
void _designatePrimaryWindow(nativeapi.Window? window) {
if (window == null) return;
final application = _application;
if (application.getPrimaryWindow() != null) return;
application.setPrimaryWindow(window);
}

/// Sets window/taskbar icon.
Expand Down Expand Up @@ -620,7 +652,11 @@ class WindowManager {
///
/// @platforms macos
Future<void> setBadgeLabel([String? label]) async {
tryNative(() => nativeapi.Application.instance.setBadgeLabel(label ?? ''));
final window = _target;
tryNative(() {
_designatePrimaryWindow(window);
_application.setBadgeLabel(label ?? '');
});
}

/// Returns `bool` - Whether the window has a shadow. On Windows, always
Expand Down
81 changes: 81 additions & 0 deletions test/legacy_taskbar_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// The taskbar progress and badge of package:window_manager/legacy.dart.
// ignore_for_file: deprecated_member_use_from_same_package

import 'package:flutter_test/flutter_test.dart';
import 'package:nativeapi/nativeapi.dart' as nativeapi;
import 'package:window_manager/legacy.dart';

// A nativeapi window simulated in Dart; no native window is created.
class _FakeNativeWindow extends nativeapi.Window {
_FakeNativeWindow() : super.borrowed(0);
}

// Only the primary window and taskbar calls the bridge makes; any other
// member throws.
class _FakeApplication implements nativeapi.Application {
nativeapi.Window? primaryWindow;
final List<String> calls = <String>[];

@override
nativeapi.Window? getPrimaryWindow() => primaryWindow;

@override
void setPrimaryWindow(nativeapi.Window? window) {
calls.add('setPrimaryWindow');
primaryWindow = window;
}

@override
bool setProgressBar(double progress) {
calls.add('setProgressBar($progress)');
return true;
}

@override
bool setBadgeLabel(String label) {
calls.add('setBadgeLabel($label)');
return true;
}

@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

void main() {
late _FakeApplication application;

setUp(() {
application = _FakeApplication();
windowManager.debugApplicationOverride = application;
});

tearDown(() {
windowManager.debugApplicationOverride = null;
windowManager.debugBindWindow(null);
});

test('progress and badge designate the window as primary', () async {
final window = _FakeNativeWindow();
windowManager.debugBindWindow(window);

await windowManager.setProgressBar(0.5);
expect(application.primaryWindow, same(window));
await windowManager.setBadgeLabel('3');
expect(application.calls, [
'setPrimaryWindow',
'setProgressBar(0.5)',
'setBadgeLabel(3)',
]);
});

test('a primary window the app chose is kept', () async {
final chosen = _FakeNativeWindow();
application.primaryWindow = chosen;
windowManager.debugBindWindow(_FakeNativeWindow());

await windowManager.setProgressBar(1);
await windowManager.setBadgeLabel();
expect(application.primaryWindow, same(chosen));
expect(application.calls, ['setProgressBar(1.0)', 'setBadgeLabel()']);
});
}