diff --git a/packages/nesd/lib/ui/toast/toaster.dart b/packages/nesd/lib/ui/toast/toaster.dart index bb812863..95e08c05 100644 --- a/packages/nesd/lib/ui/toast/toaster.dart +++ b/packages/nesd/lib/ui/toast/toaster.dart @@ -14,45 +14,60 @@ Toaster toaster(Ref ref) { } class Toaster { - Toaster({required this.state}) { - _timer = Timer.periodic(const Duration(milliseconds: 100), (_) { - _update(); - }); - } + Toaster({required this.state}); final ToastState state; - late final Timer _timer; + Timer? _timer; void dispose() { - _timer.cancel(); + _timer?.cancel(); } void send(Toast toast) { state.add(toast); + + _scheduleExpiry(); } void dismiss(Toast toast) { state.remove(toast); + + _scheduleExpiry(); } - void _update() { + void _scheduleExpiry() { + _timer?.cancel(); + _timer = null; + final top = state.top; if (top == null) { return; } - final lifetime = switch (top.type) { - ToastType.info => const Duration(seconds: 3), - ToastType.warning => const Duration(seconds: 5), - ToastType.error => const Duration(seconds: 8), - }; + final elapsed = DateTime.now().difference(top.createdAt); + final remaining = _lifetime(top) - elapsed; + + if (remaining <= Duration.zero) { + _onExpiry(); - if (DateTime.now().difference(top.createdAt) > lifetime) { - state.pop(); + return; } + + _timer = Timer(remaining, _onExpiry); } + + void _onExpiry() { + state.pop(); + _scheduleExpiry(); + } + + Duration _lifetime(Toast toast) => switch (toast.type) { + ToastType.info => const Duration(seconds: 3), + ToastType.warning => const Duration(seconds: 5), + ToastType.error => const Duration(seconds: 8), + }; } @riverpod diff --git a/packages/nesd/test/ui/robot.dart b/packages/nesd/test/ui/robot.dart index 7f0d91f9..440cbad1 100644 --- a/packages/nesd/test/ui/robot.dart +++ b/packages/nesd/test/ui/robot.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'dart:ui'; import 'package:flutter/services.dart'; @@ -13,6 +14,7 @@ import 'package:nesd/ui/nesd_app.dart'; import 'package:nesd/ui/settings/settings.dart'; import 'package:nesd/ui/settings/shared_preferences.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path/path.dart' as path; import 'package:shared_preferences/shared_preferences.dart'; import 'base_robot.dart'; @@ -86,6 +88,14 @@ class Robot extends BaseRobot { tester.view.physicalSize = const Size(1920, 1080) * tester.view.devicePixelRatio; + final tempDir = _createTempDir(); + + addTearDown(() { + if (tempDir.existsSync()) { + tempDir.deleteSync(recursive: true); + } + }); + await tester.pumpWidget( ProviderScope( overrides: [ @@ -95,7 +105,7 @@ class Robot extends BaseRobot { sharedPreferencesProvider.overrideWithValue(sharedPreferences), packageInfoProvider.overrideWithValue(packageInfo), filesystemProvider.overrideWithValue(fileSystem), - applicationSupportPathProvider.overrideWithValue('/tmp/nesd'), + applicationSupportPathProvider.overrideWithValue(tempDir.path), ], child: const NesdApp(), ), @@ -103,6 +113,14 @@ class Robot extends BaseRobot { await tester.pumpAndSettle(); } + Directory _createTempDir() { + final random = Random().nextInt(1 << 32).toRadixString(36); + final dir = Directory(path.join(Directory.systemTemp.path, 'nesd_$random')) + ..createSync(); + + return dir; + } + Future screenshot(String filename) async { await tester.runAsync(() async { final image = await captureImage(