diff --git a/wakelock_plus/example/.gitignore b/wakelock_plus/example/.gitignore index d0a10c6..fe41d4a 100644 --- a/wakelock_plus/example/.gitignore +++ b/wakelock_plus/example/.gitignore @@ -32,11 +32,10 @@ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json -!/ios/Podfile \ No newline at end of file +!/ios/Podfile diff --git a/wakelock_plus/example/web/index.html b/wakelock_plus/example/web/index.html index be820e8..14967bb 100644 --- a/wakelock_plus/example/web/index.html +++ b/wakelock_plus/example/web/index.html @@ -31,29 +31,8 @@ example - - - - - + diff --git a/wakelock_plus/assets/no_sleep.js b/wakelock_plus/lib/assets/no_sleep.js similarity index 79% rename from wakelock_plus/assets/no_sleep.js rename to wakelock_plus/lib/assets/no_sleep.js index ccfab74..d74345f 100644 --- a/wakelock_plus/assets/no_sleep.js +++ b/wakelock_plus/lib/assets/no_sleep.js @@ -1,3 +1,30 @@ +class PromiseCompleter { + _promise; + _resolve; + _reject; + constructor() { + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + isCompleted = false; + + get future() { + return this._promise; + } + + complete(value) { + this.isCompleted = true; + this._resolve(value); + } + + completeError(error) { + this._reject(error); + } +} + var webm = 'data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=' var mp4 = @@ -46,21 +73,19 @@ var oldIOS = var nativeWakeLock = 'wakeLock' in navigator var NoSleep = (function () { - var _releasedNative = true - var _nativeRequestInProgress = false + var _nativeEnabledCompleter; + var _playVideoCompleter; function NoSleep() { var _this = this _classCallCheck(this, NoSleep) + this.nativeEnabled = false if (nativeWakeLock) { this._wakeLock = null var handleVisibilityChange = function handleVisibilityChange() { - if ( - _this._wakeLock !== null && - document.visibilityState === 'visible' - ) { + if (_this._wakeLock !== null && document.visibilityState === 'visible') { _this.enable() } } @@ -106,27 +131,39 @@ var NoSleep = (function () { }, { key: 'enable', - value: function enable() { + value: async function enable() { var _this2 = this if (nativeWakeLock) { - _nativeRequestInProgress = true + // Disalbe any previously held wakelocks. + await this.disable() + if (_nativeEnabledCompleter == null) { + _nativeEnabledCompleter = new PromiseCompleter() + } navigator.wakeLock .request('screen') .then(function (wakeLock) { - _releasedNative = false - _nativeRequestInProgress = false - _this2._wakeLock = wakeLock + _this2.nativeEnabled = true + _nativeEnabledCompleter.complete() + _nativeEnabledCompleter = null + // We now have a wakelock. Notify all of the existing callers. + console.log("Wake Lock active."); _this2._wakeLock.addEventListener('release', function () { - _releasedNative = true + _this2.nativeEnabled = false _this2._wakeLock = null + console.log("Wake Lock released."); }) }) .catch(function (err) { - _nativeRequestInProgress = false - console.error(err.name + ', ' + err.message) + _this2.nativeEnabled = false + var errorMessage = err.name + ', ' + err.message + console.error(errorMessage) + _nativeEnabledCompleter.completeError(errorMessage) + _nativeEnabledCompleter = null }) + // We then wait for screen to be made available. + return _nativeEnabledCompleter.future } else if (oldIOS) { this.disable() console.warn( @@ -138,17 +175,35 @@ var NoSleep = (function () { window.setTimeout(window.stop, 0) } }, 15000) + return Promise.resolve() } else { - this.noSleepVideo.play() + if (_playVideoCompleter == null) { + _playVideoCompleter = new PromiseCompleter() + } + var playPromise = this.noSleepVideo.play() + playPromise.then(function (res) { + _playVideoCompleter.complete() + _playVideoCompleter = null + }).catch(function (err) { + var errorMessage = err.name + ', ' + err.message + console.error(errorMessage) + _playVideoCompleter.completeError(errorMessage) + _playVideoCompleter = null + }); + return _playVideoCompleter.future } }, }, { key: 'disable', - value: function disable() { + value: async function disable() { if (nativeWakeLock) { + // If we're still trying to enable the wakelock, wait for it to be enabled + if (_nativeEnabledCompleter != null) { + await _nativeEnabledCompleter.future + } if (this._wakeLock != null) { - _releasedNative = true + this.nativeEnabled = false this._wakeLock.release() } @@ -162,34 +217,30 @@ var NoSleep = (function () { this.noSleepTimer = null } } else { + if (_playVideoCompleter != null) { + await _playVideoCompleter.future + } this.noSleepVideo.pause() } + return Promise.resolve(); }, }, { - key: 'enabled', - value: async function enabled() { + key: 'isEnabled', + value: async function isEnabled() { if (nativeWakeLock) { - if (_nativeRequestInProgress == true) { - // Wait until the request is done. - while (true) { - // Wait for 42 milliseconds. - await new Promise((resolve, reject) => setTimeout(resolve, 42)) - if (_nativeRequestInProgress == false) { - break - } - } - } - - // todo: use WakeLockSentinel.released when that is available (https://developer.mozilla.org/en-US/docs/Web/API/WakeLockSentinel/released) - if (_releasedNative != false) { - return false + // If we're still trying to enable the wakelock, wait for it to be enabled + if (_nativeEnabledCompleter != null) { + await _nativeEnabledCompleter.future } - return true + return this.nativeEnabled } else if (oldIOS) { return this.noSleepTimer != null } else { + if (_playVideoCompleter != null) { + await _playVideoCompleter.future + } if (this.noSleepVideo == undefined) { return false } @@ -208,17 +259,22 @@ var noSleep = new NoSleep() var Wakelock = { enabled: async function () { try { - return noSleep.enabled() + return noSleep.isEnabled() } catch (e) { return false } }, toggle: async function (enable) { - if (enable) { - noSleep.enable() - } else { - noSleep.disable() + try { + if (enable) { + await noSleep.enable() + } else { + await noSleep.disable() + } + } catch (e) { + throw e } + return Promise.resolve() }, } diff --git a/wakelock_plus/lib/src/wakelock_plus_web_plugin.dart b/wakelock_plus/lib/src/wakelock_plus_web_plugin.dart index 73cf3f3..4ac9b19 100644 --- a/wakelock_plus/lib/src/wakelock_plus_web_plugin.dart +++ b/wakelock_plus/lib/src/wakelock_plus_web_plugin.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'dart:js_interop'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart'; import 'package:wakelock_plus/src/web_impl/import_js_library.dart'; import 'package:wakelock_plus/src/web_impl/js_wakelock.dart' as wakelock_plus_web; +import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart'; /// The web implementation of the [WakelockPlatformInterface]. /// @@ -14,30 +14,52 @@ class WakelockPlusWebPlugin extends WakelockPlusPlatformInterface { /// Registers [WakelockPlusWebPlugin] as the default instance of the /// [WakelockPlatformInterface]. static void registerWith(Registrar registrar) { - // Import a version of `NoSleep.js` that was adjusted for the wakelock - // plugin. - _jsLoaded = importJsLibrary( - url: 'assets/no_sleep.js', flutterPluginName: 'wakelock_plus'); - WakelockPlusPlatformInterface.instance = WakelockPlusWebPlugin(); } - // The future that resolves when the JS library is loaded. - static late Future _jsLoaded; + // The future that signals when the JS is loaded. + // This needs to be `await`ed before accessing any methods of the + // JS-interop layer. + late Future _jsLoaded; + bool _jsLibraryLoaded = false; + + // + // Lazily imports the JS library once, then awaits to ensure that + // it's loaded into the DOM. + // + Future _ensureJsLoaded() async { + if (!_jsLibraryLoaded) { + _jsLoaded = importJsLibrary( + url: 'assets/no_sleep.js', flutterPluginName: 'wakelock_plus'); + _jsLibraryLoaded = true; + } + await _jsLoaded; + } @override Future toggle({required bool enable}) async { // Make sure the JS library is loaded before calling it. - await _jsLoaded; + await _ensureJsLoaded(); + final completer = Completer(); - wakelock_plus_web.toggle(enable); + wakelock_plus_web.toggle(enable).toDart.then( + // onResolve + (value) { + completer.complete(); + }, + // onReject + onError: (error) { + completer.completeError(error); + }, + ); + + return completer.future; } @override Future get enabled async { // Make sure the JS library is loaded before calling it. - await _jsLoaded; - + await _ensureJsLoaded(); final completer = Completer(); wakelock_plus_web.enabled().toDart.then( diff --git a/wakelock_plus/lib/src/web_impl/import_js_library.dart b/wakelock_plus/lib/src/web_impl/import_js_library.dart index b998c6d..562d77b 100644 --- a/wakelock_plus/lib/src/web_impl/import_js_library.dart +++ b/wakelock_plus/lib/src/web_impl/import_js_library.dart @@ -1,6 +1,7 @@ import 'dart:js_interop'; +import 'dart:ui_web' as ui_web; -import 'package:web/web.dart'; +import 'package:web/web.dart' as web; /// This is an implementation of the `import_js_library` plugin that is used /// until that plugin is migrated to null safety. @@ -18,19 +19,23 @@ Future importJsLibrary( } String _libraryUrl(String url, String pluginName) { + // Added suggested changes as per + // https://github.com/fluttercommunity/wakelock_plus/issues/19#issuecomment-2301963609 if (url.startsWith('./')) { url = url.replaceFirst('./', ''); - return './assets/packages/$pluginName/$url'; } + if (url.startsWith('assets/')) { - return './assets/packages/$pluginName/$url'; - } else { - return url; + return ui_web.assetManager.getAssetUrl( + 'packages/$pluginName/$url', + ); } + + return url; } -HTMLScriptElement _createScriptTag(String library) { - final script = document.createElement('script') as HTMLScriptElement +web.HTMLScriptElement _createScriptTag(String library) { + final script = web.document.createElement('script') as web.HTMLScriptElement ..type = 'text/javascript' ..charset = 'utf-8' ..async = true @@ -42,13 +47,25 @@ HTMLScriptElement _createScriptTag(String library) { /// Future that resolves when all load. Future _importJSLibraries(List libraries) { final loading = >[]; - final head = document.head; + final head = web.document.head; for (final library in libraries) { if (!_isImported(library)) { final scriptTag = _createScriptTag(library); head!.appendChild(scriptTag); loading.add(scriptTag.onLoad.first); + scriptTag.onError.listen((event) { + final scriptElement = event.target.isA() + ? event.target as web.HTMLScriptElement + : null; + if (scriptElement != null) { + loading.add( + Future.error( + Exception('Error loading: ${scriptElement.src}'), + ), + ); + } + }); } } @@ -56,18 +73,18 @@ Future _importJSLibraries(List libraries) { } bool _isImported(String url) { - final head = document.head!; + final head = web.document.head!; return _isLoaded(head, url); } -bool _isLoaded(HTMLHeadElement head, String url) { +bool _isLoaded(web.HTMLHeadElement head, String url) { if (url.startsWith('./')) { url = url.replaceFirst('./', ''); } for (int i = 0; i < head.children.length; i++) { final element = head.children.item(i)!; if (element.instanceOfString('HTMLScriptElement')) { - if ((element as HTMLScriptElement).src.endsWith(url)) { + if ((element as web.HTMLScriptElement).src.endsWith(url)) { return true; } } diff --git a/wakelock_plus/lib/src/web_impl/js_wakelock.dart b/wakelock_plus/lib/src/web_impl/js_wakelock.dart index 38b7822..bbf2351 100644 --- a/wakelock_plus/lib/src/web_impl/js_wakelock.dart +++ b/wakelock_plus/lib/src/web_impl/js_wakelock.dart @@ -5,7 +5,7 @@ import 'dart:js_interop'; /// Toggles the JS wakelock. @JS() -external void toggle(bool enable); +external JSPromise toggle(bool enable); /// Returns a JS promise of whether the wakelock is enabled or not. @JS() diff --git a/wakelock_plus/pubspec.yaml b/wakelock_plus/pubspec.yaml index 1815519..6a2e1fb 100644 --- a/wakelock_plus/pubspec.yaml +++ b/wakelock_plus/pubspec.yaml @@ -99,4 +99,4 @@ flutter: # https://flutter.dev/custom-fonts/#from-packages assets: - - assets/no_sleep.js + - packages/wakelock_plus/assets/no_sleep.js diff --git a/wakelock_plus/test/wakelock_plus_web_plugin_test.dart b/wakelock_plus/test/wakelock_plus_web_plugin_test.dart index 869b576..f205f1d 100644 --- a/wakelock_plus/test/wakelock_plus_web_plugin_test.dart +++ b/wakelock_plus/test/wakelock_plus_web_plugin_test.dart @@ -6,13 +6,21 @@ import 'package:wakelock_plus/src/wakelock_plus_web_plugin.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus_platform_interface/wakelock_plus_platform_interface.dart'; +/// +/// Run these tests with: +/// flutter run -d chrome test/wakelock_plus_web_plugin_test.dart +/// void main() { group('$WakelockPlusWebPlugin', () { setUpAll(() async { - // todo: the web tests do not work as the JS library import does not work. + // todo: the web tests do not work as the JS library import does not work when using flutter run test --platform chrome. WakelockPlusPlatformInterface.instance = WakelockPlusWebPlugin(); }); + tearDown(() async { + await WakelockPlus.disable(); + }); + test('$WakelockPlusWebPlugin set as default instance', () { expect( WakelockPlusPlatformInterface.instance, isA()); @@ -27,7 +35,23 @@ void main() { expect(WakelockPlus.enabled, completion(isTrue)); }); + test('enable more than once', () async { + await WakelockPlus.enable(); + await WakelockPlus.enable(); + await WakelockPlus.enable(); + expect(WakelockPlus.enabled, completion(isTrue)); + }); + test('disable', () async { + await WakelockPlus.enable(); + await WakelockPlus.disable(); + expect(WakelockPlus.enabled, completion(isFalse)); + }); + + test('disable more than once', () async { + await WakelockPlus.enable(); + await WakelockPlus.disable(); + await WakelockPlus.disable(); await WakelockPlus.disable(); expect(WakelockPlus.enabled, completion(isFalse)); });