mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
fix: automatically submit scheme extension installs
This commit is contained in:
1 parent
1c57b07575
commit
6572e9e899
2 files changed
+54
-18
No files matched your search
@@ -46,6 +46,7 @@ class _ExtensionsPageState extends ConsumerState<ExtensionsPage> {
|
||||
final _searchController = TextEditingController();
|
||||
final _installController = TextEditingController();
|
||||
final _installAnchorKey = GlobalKey();
|
||||
final _installPopoverKey = GlobalKey<_InstallPopoverState>();
|
||||
bool _installDevMode = false;
|
||||
final _listScrollController = ScrollController();
|
||||
final Map<String, TextEditingController> _settingControllers = {};
|
||||
@@ -84,12 +85,20 @@ class _ExtensionsPageState extends ConsumerState<ExtensionsPage> {
|
||||
// after the toolbar is laid out, so cold and warm links share the same UI.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !identical(ref.read(pendingExtensionInstallProvider), pendingInstall)) return;
|
||||
if (_installPopoverKey.currentState?._installing == true) return;
|
||||
final anchorContext = _installAnchorKey.currentContext;
|
||||
if (anchorContext == null) return;
|
||||
_installController.text = pendingInstall.url;
|
||||
_installDevMode = pendingInstall.devMode;
|
||||
_showInstallPopover(anchorContext);
|
||||
ref.read(pendingExtensionInstallProvider.notifier).clear();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !identical(ref.read(pendingExtensionInstallProvider), pendingInstall)) return;
|
||||
final popover = _installPopoverKey.currentState;
|
||||
if (popover == null || popover._installing) return;
|
||||
ref.read(pendingExtensionInstallProvider.notifier).clear();
|
||||
unawaited(popover._install());
|
||||
});
|
||||
WidgetsBinding.instance.ensureVisualUpdate();
|
||||
});
|
||||
}
|
||||
final stateAsync = ref.watch(extensionsControllerProvider);
|
||||
@@ -214,7 +223,14 @@ class _ExtensionsPageState extends ConsumerState<ExtensionsPage> {
|
||||
offset: const Offset(0, 8),
|
||||
modal: false,
|
||||
consumeOutsideTaps: false,
|
||||
builder: (context) => _InstallPopover(controller: _installController, onInstallUrl: _installFromUrl),
|
||||
builder: (context) => _InstallPopover(
|
||||
key: _installPopoverKey,
|
||||
controller: _installController,
|
||||
onInstallUrl: _installFromUrl,
|
||||
onFinished: () {
|
||||
if (mounted && ref.read(pendingExtensionInstallProvider) != null) setState(() {});
|
||||
},
|
||||
),
|
||||
);
|
||||
_installPopover = overlay;
|
||||
unawaited(
|
||||
@@ -815,10 +831,11 @@ class _OutlineToolbarIconButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _InstallPopover extends StatefulWidget {
|
||||
const _InstallPopover({required this.controller, required this.onInstallUrl});
|
||||
const _InstallPopover({super.key, required this.controller, required this.onInstallUrl, required this.onFinished});
|
||||
|
||||
final TextEditingController controller;
|
||||
final Future<void> Function() onInstallUrl;
|
||||
final VoidCallback onFinished;
|
||||
|
||||
@override
|
||||
State<_InstallPopover> createState() => _InstallPopoverState();
|
||||
@@ -880,8 +897,8 @@ class _InstallPopoverState extends State<_InstallPopover> {
|
||||
setState(() => _installing = true);
|
||||
await widget.onInstallUrl();
|
||||
if (!mounted) return;
|
||||
setState(() => _installing = false);
|
||||
await shad.closeOverlay(context);
|
||||
widget.onFinished();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -889,10 +889,10 @@ void main() {
|
||||
(1100.0, TargetPlatform.macOS),
|
||||
]) {
|
||||
testWidgets(
|
||||
'scheme install prefills URL before and after mounting at width $width',
|
||||
'scheme install automatically runs before and after mounting at width $width',
|
||||
(tester) async {
|
||||
await _setTestSize(tester, Size(width, 900));
|
||||
final controller = FakeExtensionsController();
|
||||
final controller = FakeExtensionsController()..installGate = Completer<void>();
|
||||
final container = ProviderContainer(overrides: [extensionsControllerProvider.overrideWith(() => controller)]);
|
||||
addTearDown(container.dispose);
|
||||
const firstUrl = 'https://github.com/author/扩展';
|
||||
@@ -912,31 +912,48 @@ void main() {
|
||||
final input = find.byKey(const ValueKey('extension-install-url-input'));
|
||||
expect(tester.widget<AppTextField>(input).controller!.text, firstUrl);
|
||||
expect(container.read(pendingExtensionInstallProvider), isNull);
|
||||
expect(controller.installCalls, 0);
|
||||
expect(controller.installCalls, 1);
|
||||
expect(controller.lastInstallUrl, firstUrl);
|
||||
final popoverRect = tester.getRect(find.byKey(const ValueKey('extension-install-popover')));
|
||||
expect(popoverRect.left, greaterThanOrEqualTo(0));
|
||||
expect(popoverRect.right, lessThanOrEqualTo(width));
|
||||
expect(find.descendant(of: input, matching: find.byType(shad.CircularProgressIndicator)), findsOneWidget);
|
||||
|
||||
// A second link updates the already-open input without installing twice.
|
||||
// Rebuilds and manual submission must not duplicate an active install.
|
||||
await tester.tap(find.descendant(of: input, matching: find.byType(shad.IconButton)));
|
||||
await tester.pump();
|
||||
expect(controller.installCalls, 1);
|
||||
controller.installGate!.complete();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('extension-install-popover')), findsNothing);
|
||||
|
||||
// A warm link automatically installs from an already-open form too.
|
||||
await tester.tap(find.byKey(const ValueKey('install-extension-button')));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
const secondUrl = 'https://github.com/author/second';
|
||||
controller.installGate = Completer<void>();
|
||||
container.read(pendingExtensionInstallProvider.notifier).set(InstallExtension(url: secondUrl));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(tester.widget<AppTextField>(input).controller!.text, secondUrl);
|
||||
expect(find.byKey(const ValueKey('extension-install-popover')), findsOneWidget);
|
||||
expect(controller.installCalls, 0);
|
||||
await tester.tap(find.descendant(of: input, matching: find.byType(shad.IconButton)));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.installCalls, 1);
|
||||
expect(controller.installCalls, 2);
|
||||
expect(controller.lastInstallUrl, secondUrl);
|
||||
|
||||
// A warm link also reopens a dismissed form.
|
||||
// A request arriving during installation waits until the active form closes.
|
||||
container.read(pendingExtensionInstallProvider.notifier).set(InstallExtension(url: firstUrl));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(tester.widget<AppTextField>(input).controller!.text, firstUrl);
|
||||
expect(controller.installCalls, 1);
|
||||
expect(controller.installCalls, 2);
|
||||
controller.installGate!.complete();
|
||||
controller.installGate = Completer<void>();
|
||||
for (var i = 0; i < 8; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
}
|
||||
expect(controller.installCalls, 3);
|
||||
expect(controller.lastInstallUrl, firstUrl);
|
||||
controller.installGate!.complete();
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump();
|
||||
@@ -5050,11 +5067,13 @@ class FailingTasksController extends TasksController {
|
||||
class FakeExtensionsController extends ExtensionsController {
|
||||
int installCalls = 0;
|
||||
String? lastInstallUrl;
|
||||
Completer<void>? installGate;
|
||||
|
||||
@override
|
||||
Future<void> installFromUrl(String url, {bool devInstall = false}) async {
|
||||
installCalls++;
|
||||
lastInstallUrl = url;
|
||||
await installGate?.future;
|
||||
}
|
||||
|
||||
int removeCalls = 0;
|
||||
|
||||
Reference in new issue
Block a user