diff --git a/flutter-idea/src/io/flutter/FlutterInitializer.java b/flutter-idea/src/io/flutter/FlutterInitializer.java index e87e0e2df..6ae608ea8 100644 --- a/flutter-idea/src/io/flutter/FlutterInitializer.java +++ b/flutter-idea/src/io/flutter/FlutterInitializer.java @@ -364,7 +364,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { }); } - private void initializeToolWindows(@NotNull Project project) { + private void initializeToolWindows(@NotNull final Project project) { // Start watching for Flutter debug active events. FlutterViewFactory.init(project); RemainingDevToolsViewFactory.init(project); diff --git a/flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java b/flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java index b7048a08e..d6f84210f 100644 --- a/flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java +++ b/flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java @@ -6,75 +6,49 @@ package io.flutter.deeplinks; import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowFactory; -import io.flutter.FlutterUtils; -import io.flutter.actions.RefreshToolWindowAction; import io.flutter.bazel.WorkspaceCache; +import io.flutter.devtools.AbstractDevToolsViewFactory; import io.flutter.devtools.DevToolsIdeFeature; import io.flutter.devtools.DevToolsUrl; -import io.flutter.run.daemon.DevToolsService; -import io.flutter.sdk.FlutterSdk; +import io.flutter.run.daemon.DevToolsInstance; import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.OpenApiUtils; -import kotlin.coroutines.Continuation; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Optional; +public class DeepLinksViewFactory extends AbstractDevToolsViewFactory { + @NotNull public static String TOOL_WINDOW_ID = "Flutter Deep Links"; -public class DeepLinksViewFactory implements ToolWindowFactory { - @NotNull private static String TOOL_WINDOW_ID = "Flutter Deep Links"; + @NotNull public static String DEVTOOLS_PAGE_ID = "deep-links"; @Override - public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - return sdkVersion != null && sdkVersion.canUseDeepLinksTool(); + public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) { + return flutterSdkVersion.canUseDeepLinksTool(); } @Override - public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - - AsyncUtils.whenCompleteUiThread( - DevToolsService.getInstance(project).getDevToolsInstance(), - (instance, error) -> { - // Skip displaying if the project has been closed. - if (!project.isOpen()) { - return; - } - - if (error != null) { - return; - } - - if (instance == null) { - return; - } - - final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() - .setDevToolsHost(instance.host()) - .setDevToolsPort(instance.port()) - .setPage("deep-links") - .setEmbed(true) - .setFlutterSdkVersion(sdkVersion) - .setWorkspaceCache(WorkspaceCache.getInstance(project)) - .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) - .build(); + @NotNull + public String getToolWindowId() { + return TOOL_WINDOW_ID; + } - OpenApiUtils.safeInvokeLater(() -> { - Optional.ofNullable( - FlutterUtils.embeddedBrowser(project)) - .ifPresent(embeddedBrowser -> embeddedBrowser.openPanel(toolWindow, "Deep Links", devToolsUrl, System.out::println)); - }); - } - ); + @Override + @NotNull + public String getToolWindowTitle() { + return "Deep Links"; + } - // TODO(helin24): It may be better to add this to the gear actions or to attach as a mouse event on individual tabs within a tool - // window, but I wasn't able to get either working immediately. - toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID))); + @Override + @NotNull + public DevToolsUrl getDevToolsUrl(@NotNull final Project project, + @NotNull final FlutterSdkVersion flutterSdkVersion, + @NotNull final DevToolsInstance instance) { + return new DevToolsUrl.Builder() + .setDevToolsHost(instance.host()) + .setDevToolsPort(instance.port()) + .setPage(DEVTOOLS_PAGE_ID) + .setEmbed(true) + .setFlutterSdkVersion(flutterSdkVersion) + .setWorkspaceCache(WorkspaceCache.getInstance(project)) + .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) + .build(); } -} +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/devtools/AbstractDevToolsViewFactory.java b/flutter-idea/src/io/flutter/devtools/AbstractDevToolsViewFactory.java new file mode 100644 index 000000000..e46e52821 --- /dev/null +++ b/flutter-idea/src/io/flutter/devtools/AbstractDevToolsViewFactory.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.devtools; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; +import io.flutter.FlutterUtils; +import io.flutter.actions.RefreshToolWindowAction; +import io.flutter.run.daemon.DevToolsInstance; +import io.flutter.run.daemon.DevToolsService; +import io.flutter.sdk.FlutterSdk; +import io.flutter.sdk.FlutterSdkVersion; +import io.flutter.utils.AsyncUtils; +import io.flutter.utils.OpenApiUtils; +import io.flutter.view.EmbeddedBrowser; +import io.flutter.view.ViewUtils; +import kotlin.coroutines.Continuation; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public abstract class AbstractDevToolsViewFactory implements ToolWindowFactory { + + @NotNull + protected final ViewUtils viewUtils = new ViewUtils(); + + public abstract boolean versionSupportsThisTool(@NotNull FlutterSdkVersion flutterSdkVersion); + + @NotNull + public abstract String getToolWindowId(); + + @NotNull + public abstract String getToolWindowTitle(); + + @NotNull + public abstract DevToolsUrl getDevToolsUrl(@NotNull Project project, + @NotNull FlutterSdkVersion flutterSdkVersion, + @NotNull DevToolsInstance instance); + + protected void doAfterBrowserOpened(@NotNull Project project, @NotNull EmbeddedBrowser browser) {} + + @Override + public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) { + // Due to https://github.com/flutter/flutter/issues/142521, this always returns true when the + // Flutter IJ plugin is installed. + + // The logic which asserts that the Flutter SDK is up to date enough for this particular feature + // is captured in the implementation of createToolWindowContent() below. + + return true; + } + + @Override + public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { + final FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(project); + final FlutterSdkVersion flutterSdkVersion = flutterSdk == null ? null : flutterSdk.getVersion(); + + // There are four potential states for the Flutter SDK: + // 1. The Flutter SDK is null (flutterSdk == null) (an SDK path is invalid or not configured) + // 2. The Flutter SDK exists, but version file information is unavailable (flutterSdkVersion == null), + // see https://github.com/flutter/flutter/issues/142521. + // 3. The Flutter SDK exists, and the version file information is available, but this tool is not + // available on the version of the SDK that the user has. + // 4. The Flutter SDK exists with valid version information. + + // First case: + if (flutterSdk == null) { + viewUtils.presentLabels(toolWindow, List.of("Set the Flutter SDK path in", + "Settings > Languages & Frameworks > Flutter,", + "and then restart the IDE.")); + return; + } + + // Second case: + if (flutterSdk.getVersion().fullVersion().equals(FlutterSdkVersion.UNKNOWN_VERSION)) { + viewUtils.presentLabels(toolWindow, List.of("A Flutter SDK was found at the location", + "specified in the settings, however the directory", + "is in an incomplete state. To fix, shut down the IDE,", + "run `flutter doctor` or `flutter --version`", + "and then restart the IDE.")); + return; + } + + // Third case: + if (!versionSupportsThisTool(flutterSdkVersion)) { + final String versionText = flutterSdkVersion.fullVersion(); + viewUtils.presentLabels(toolWindow, List.of("The version of your Flutter SDK,", + versionText + ",", + "is not recent enough to use this tool.", + "Update the Flutter SDK, `flutter upgrade`,", + "and then restart the IDE.")); + return; + } + + // Final case: + AsyncUtils.whenCompleteUiThread( + DevToolsService.getInstance(project).getDevToolsInstance(), + (instance, error) -> { + // Skip displaying if the project has been closed. + if (!project.isOpen()) { + return; + } + + if (error != null) { + return; + } + + if (instance == null) { + return; + } + + final DevToolsUrl devToolsUrl = getDevToolsUrl(project, flutterSdkVersion, instance); + + OpenApiUtils.safeInvokeLater(() -> { + Optional.ofNullable( + FlutterUtils.embeddedBrowser(project)) + .ifPresent(embeddedBrowser -> + { + embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), devToolsUrl, System.out::println); + doAfterBrowserOpened(project, embeddedBrowser); + }); + }); + } + ); + + // TODO(helin24): It may be better to add this to the gear actions or to attach as a mouse event on individual tabs within a tool + // window, but I wasn't able to get either working immediately. + toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(getToolWindowId()))); + } +} \ No newline at end of file diff --git a/flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java b/flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java index 1c65bae26..beb85961c 100644 --- a/flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java +++ b/flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java @@ -6,92 +6,64 @@ package io.flutter.devtools; import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowFactory; -import com.intellij.ui.content.ContentManager; -import io.flutter.FlutterUtils; -import io.flutter.actions.RefreshToolWindowAction; import io.flutter.bazel.WorkspaceCache; -import io.flutter.run.daemon.DevToolsService; -import io.flutter.sdk.FlutterSdk; +import io.flutter.run.daemon.DevToolsInstance; import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.OpenApiUtils; +import io.flutter.view.EmbeddedBrowser; import io.flutter.view.FlutterViewMessages; -import kotlin.coroutines.Continuation; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Optional; +public class DevToolsExtensionsViewFactory extends AbstractDevToolsViewFactory { + @NotNull public static String TOOL_WINDOW_ID = "Flutter DevTools Extensions"; -public class DevToolsExtensionsViewFactory implements ToolWindowFactory { - @NotNull private static String TOOL_WINDOW_ID = "Flutter DevTools Extensions"; - - public static void init(Project project) { + public static void init(@NotNull final Project project) { project.getMessageBus().connect().subscribe( FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event) ); } - private static void initView(Project project, FlutterViewMessages.FlutterDebugEvent event) { + private static void initView(@NotNull final Project project, FlutterViewMessages.FlutterDebugEvent event) { DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class); String vmServiceUri = event.app.getConnector().getBrowserUrl(); - if (vmServiceUri == null) return; + if (service == null || vmServiceUri == null) return; service.updateVmServiceUri(vmServiceUri); } @Override - public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow window) { - final ContentManager contentManager = window.getContentManager(); - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class); - - AsyncUtils.whenCompleteUiThread( - DevToolsService.getInstance(project).getDevToolsInstance(), - (instance, error) -> { - // Skip displaying if the project has been closed. - if (!project.isOpen()) { - return; - } - - if (error != null) { - return; - } - - if (instance == null) { - return; - } + public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) { + return flutterSdkVersion.canUseDevToolsMultiEmbed(); + } - final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() - .setDevToolsHost(instance.host()) - .setDevToolsPort(instance.port()) - .setHide("all-except-extensions") - .setEmbed(true).setFlutterSdkVersion(sdkVersion) - .setWorkspaceCache(WorkspaceCache.getInstance(project)) - .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) - .build(); + @Override + @NotNull + public String getToolWindowId() { + return TOOL_WINDOW_ID; + } - OpenApiUtils.safeInvokeLater(() -> { - Optional.ofNullable( - FlutterUtils.embeddedBrowser(project)) - .ifPresent(embeddedBrowser -> { - embeddedBrowser.openPanel(window, "Flutter DevTools", devToolsUrl, System.out::println); - service.setEmbeddedBrowser(embeddedBrowser); - }); - }); - } - ); + @NotNull + public String getToolWindowTitle() { + return "Flutter DevTools"; + } - window.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID))); + @Override + protected void doAfterBrowserOpened(@NotNull final Project project, @NotNull final EmbeddedBrowser embeddedBrowser) { + DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class); + if (service == null) return; + service.setEmbeddedBrowser(embeddedBrowser); } - @Nullable @Override - public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - return sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed(); + @NotNull + public DevToolsUrl getDevToolsUrl(@NotNull final Project project, + @NotNull final FlutterSdkVersion flutterSdkVersion, + @NotNull final DevToolsInstance instance) { + return new DevToolsUrl.Builder() + .setDevToolsHost(instance.host()) + .setDevToolsPort(instance.port()) + .setHide("all-except-extensions") + .setEmbed(true).setFlutterSdkVersion(flutterSdkVersion) + .setWorkspaceCache(WorkspaceCache.getInstance(project)) + .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) + .build(); } } diff --git a/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java b/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java index 8646b8346..ce9ed21d4 100644 --- a/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java +++ b/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java @@ -56,66 +56,79 @@ public static class Builder { public Builder() {} + @NotNull public Builder setDevToolsHost(String devToolsHost) { this.devToolsHost = devToolsHost; return this; } + @NotNull public Builder setDevToolsPort(int devToolsPort) { this.devToolsPort = devToolsPort; return this; } + @NotNull public Builder setVmServiceUri(String vmServiceUri) { this.vmServiceUri = vmServiceUri; return this; } + @NotNull public Builder setPage(String page) { this.page = page; return this; } + @NotNull public Builder setEmbed(Boolean embed) { this.embed = embed; return this; } + @NotNull public Builder setWidgetId(String widgetId) { this.widgetId = widgetId; return this; } + @NotNull public Builder setHide(String hide) { this.hide = hide; return this; } + @NotNull public Builder setDevToolsUtils(DevToolsUtils devToolsUtils) { this.devToolsUtils = devToolsUtils; return this; } + @NotNull public Builder setFlutterSdkVersion(FlutterSdkVersion sdkVersion) { this.flutterSdkVersion = sdkVersion; return this; } + @NotNull public Builder setWorkspaceCache(WorkspaceCache workspaceCache) { this.workspaceCache = workspaceCache; return this; } + @NotNull public Builder setIdeFeature(DevToolsIdeFeature ideFeature) { this.ideFeature = ideFeature; return this; } + @NotNull public Builder setFlutterSdkUtil(FlutterSdkUtil flutterSdkUtil) { this.flutterSdkUtil = flutterSdkUtil; return this; } + @NotNull public DevToolsUrl build() { if (devToolsUtils == null) { devToolsUtils = new DevToolsUtils(); diff --git a/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java index 62adf042b..86826e5ff 100644 --- a/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java +++ b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java @@ -6,93 +6,63 @@ package io.flutter.devtools; import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowFactory; -import com.intellij.ui.content.ContentManager; -import io.flutter.FlutterUtils; -import io.flutter.actions.RefreshToolWindowAction; import io.flutter.bazel.WorkspaceCache; -import io.flutter.run.daemon.DevToolsService; -import io.flutter.sdk.FlutterSdk; +import io.flutter.run.daemon.DevToolsInstance; import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.OpenApiUtils; +import io.flutter.view.EmbeddedBrowser; import io.flutter.view.FlutterViewMessages; -import kotlin.coroutines.Continuation; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Optional; +public class RemainingDevToolsViewFactory extends AbstractDevToolsViewFactory { + @NotNull public static String TOOL_WINDOW_ID = "Flutter DevTools"; -public class RemainingDevToolsViewFactory implements ToolWindowFactory { - @NotNull private static String TOOL_WINDOW_ID = "Flutter DevTools"; - - public static void init(Project project) { - project.getMessageBus().connect().subscribe( - FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event) - ); + public static void init(@NotNull final Project project) { + project.getMessageBus().connect() + .subscribe(FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event)); } - private static void initView(Project project, FlutterViewMessages.FlutterDebugEvent event) { + private static void initView(@NotNull final Project project, FlutterViewMessages.FlutterDebugEvent event) { RemainingDevToolsViewService service = project.getService(RemainingDevToolsViewService.class); String vmServiceUri = event.app.getConnector().getBrowserUrl(); - if (vmServiceUri == null) return; + if (service == null || vmServiceUri == null) return; service.updateVmServiceUri(vmServiceUri); } - @Override - public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow window) { - final ContentManager contentManager = window.getContentManager(); - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - RemainingDevToolsViewService service = project.getService(RemainingDevToolsViewService.class); - - AsyncUtils.whenCompleteUiThread( - DevToolsService.getInstance(project).getDevToolsInstance(), - (instance, error) -> { - // Skip displaying if the project has been closed. - if (!project.isOpen()) { - return; - } - - if (error != null) { - return; - } - - if (instance == null) { - return; - } + public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) { + return flutterSdkVersion.canUseDevToolsMultiEmbed(); + } - final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() - .setDevToolsHost(instance.host()) - .setDevToolsPort(instance.port()) - .setHide("home,inspector,deep-links,extensions,debugger") - .setEmbed(true).setFlutterSdkVersion(sdkVersion) - .setWorkspaceCache(WorkspaceCache.getInstance(project)) - .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) - .build(); + @Override + @NotNull + public String getToolWindowId() { + return TOOL_WINDOW_ID; + } - OpenApiUtils.safeInvokeLater(() -> { - Optional.ofNullable( - FlutterUtils.embeddedBrowser(project)) - .ifPresent(embeddedBrowser -> { - embeddedBrowser.openPanel(window, "Flutter DevTools", devToolsUrl, System.out::println); - service.setEmbeddedBrowser(embeddedBrowser); - }); - }); - } - ); + @NotNull + public String getToolWindowTitle() { + return "Flutter DevTools"; + } - window.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID))); + @Override + protected void doAfterBrowserOpened(@NotNull final Project project, @NotNull final EmbeddedBrowser embeddedBrowser) { + RemainingDevToolsViewService service = project.getService(RemainingDevToolsViewService.class); + if (service == null) return; + service.setEmbeddedBrowser(embeddedBrowser); } - @Nullable @Override - public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - return sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed(); + @NotNull + public DevToolsUrl getDevToolsUrl(@NotNull final Project project, + @NotNull final FlutterSdkVersion flutterSdkVersion, + @NotNull final DevToolsInstance instance) { + return new DevToolsUrl.Builder() + .setDevToolsHost(instance.host()) + .setDevToolsPort(instance.port()) + .setHide("home,inspector,deep-links,extensions,debugger") + .setEmbed(true).setFlutterSdkVersion(flutterSdkVersion) + .setWorkspaceCache(WorkspaceCache.getInstance(project)) + .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) + .build(); } } diff --git a/flutter-idea/src/io/flutter/propertyeditor/PropertyEditorViewFactory.java b/flutter-idea/src/io/flutter/propertyeditor/PropertyEditorViewFactory.java index 2a0c01737..49f2906cd 100644 --- a/flutter-idea/src/io/flutter/propertyeditor/PropertyEditorViewFactory.java +++ b/flutter-idea/src/io/flutter/propertyeditor/PropertyEditorViewFactory.java @@ -7,84 +7,64 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowFactory; -import io.flutter.FlutterUtils; -import io.flutter.actions.RefreshToolWindowAction; import io.flutter.bazel.WorkspaceCache; import io.flutter.dart.DartPlugin; import io.flutter.dart.DartPluginVersion; +import io.flutter.devtools.AbstractDevToolsViewFactory; import io.flutter.devtools.DevToolsIdeFeature; import io.flutter.devtools.DevToolsUrl; -import io.flutter.run.daemon.DevToolsService; -import io.flutter.sdk.FlutterSdk; +import io.flutter.run.daemon.DevToolsInstance; import io.flutter.sdk.FlutterSdkVersion; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.OpenApiUtils; -import io.flutter.view.ViewUtils; -import kotlin.coroutines.Continuation; import org.jetbrains.annotations.NotNull; import java.util.List; -import java.util.Optional; -public class PropertyEditorViewFactory implements ToolWindowFactory { - @NotNull private static String TOOL_WINDOW_ID = "Flutter Property Editor"; +public class PropertyEditorViewFactory extends AbstractDevToolsViewFactory { + @NotNull public static String TOOL_WINDOW_ID = "Flutter Property Editor"; + @NotNull public static String DEVTOOLS_PAGE_ID = "propertyEditor"; + + @Override + public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) { + return flutterSdkVersion.canUsePropertyEditor(); + } + + @Override @NotNull - private final ViewUtils viewUtils = new ViewUtils(); + public String getToolWindowId() { + return TOOL_WINDOW_ID; + } @Override - public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); - return sdkVersion != null && sdkVersion.canUsePropertyEditor(); + @NotNull + public String getToolWindowTitle() { + return "Property Editor"; } @Override - public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { - FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); - FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); + @NotNull + public DevToolsUrl getDevToolsUrl(@NotNull final Project project, + @NotNull final FlutterSdkVersion flutterSdkVersion, + @NotNull final DevToolsInstance instance) { + return new DevToolsUrl.Builder() + .setDevToolsHost(instance.host()) + .setDevToolsPort(instance.port()) + .setPage(DEVTOOLS_PAGE_ID) + .setEmbed(true) + .setFlutterSdkVersion(flutterSdkVersion) + .setWorkspaceCache(WorkspaceCache.getInstance(project)) + .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) + .build(); + } + @Override + public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { final DartPluginVersion dartPluginVersion = DartPlugin.getDartPluginVersion(); - if (dartPluginVersion == null || !dartPluginVersion.supportsPropertyEditor()) { - viewUtils.presentLabel(toolWindow, "Flutter Property Editor requires a newer version of the Dart plugin."); + if (!dartPluginVersion.supportsPropertyEditor()) { + super.viewUtils.presentLabels(toolWindow, List.of("The Flutter Property Editor requires a", + "newer version of the Dart plugin.")); return; } - - AsyncUtils.whenCompleteUiThread( - DevToolsService.getInstance(project).getDevToolsInstance(), - (instance, error) -> { - // Skip displaying if the project has been closed. - if (!project.isOpen()) { - return; - } - - if (error != null) { - return; - } - - if (instance == null) { - return; - } - - final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() - .setDevToolsHost(instance.host()) - .setDevToolsPort(instance.port()) - .setPage("propertyEditor") - .setEmbed(true) - .setFlutterSdkVersion(sdkVersion) - .setWorkspaceCache(WorkspaceCache.getInstance(project)) - .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) - .build(); - - OpenApiUtils.safeInvokeLater(() -> { - Optional.ofNullable( - FlutterUtils.embeddedBrowser(project)) - .ifPresent(embeddedBrowser -> embeddedBrowser.openPanel(toolWindow, "Property Editor", devToolsUrl, System.out::println)); - }); - } - ); - - toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID))); + super.createToolWindowContent(project, toolWindow); } } diff --git a/flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java b/flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java index f4ccd9547..ac4f37b43 100644 --- a/flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java +++ b/flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java @@ -49,6 +49,9 @@ public final class FlutterSdkVersion implements Comparable<FlutterSdkVersion> { @NotNull public static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.32.0-0.1.pre"); + @NotNull + public static final String UNKNOWN_VERSION = "unknown version"; + @Nullable private final Version version; @Nullable @@ -153,7 +156,7 @@ public boolean isValid() { @NotNull public String fullVersion() { - return version == null ? "unknown version" : Objects.requireNonNull(version.toString()); + return version == null ? UNKNOWN_VERSION : Objects.requireNonNull(version.toString()); } /** @@ -167,7 +170,7 @@ public String getVersionText() { @Override @NotNull public String toString() { - return version == null ? "unknown version" : Objects.requireNonNull(version.toCompactString()); + return version == null ? UNKNOWN_VERSION : Objects.requireNonNull(version.toCompactString()); } @Override diff --git a/flutter-idea/src/io/flutter/view/ViewUtils.java b/flutter-idea/src/io/flutter/view/ViewUtils.java index 8564ace95..a96907b2a 100644 --- a/flutter-idea/src/io/flutter/view/ViewUtils.java +++ b/flutter-idea/src/io/flutter/view/ViewUtils.java @@ -15,6 +15,7 @@ import com.intellij.util.ui.UIUtil; import io.flutter.utils.LabelInput; import io.flutter.utils.OpenApiUtils; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; @@ -27,6 +28,33 @@ public void presentLabel(ToolWindow toolWindow, String text) { replacePanelLabel(toolWindow, label); } + /** + * Displays multiple labels vertically centered in the tool window. + * + * @param toolWindow The target tool window. + * @param labels A list of strings to display as labels. + */ + public void presentLabels(@NotNull ToolWindow toolWindow, @NotNull List<String> labels) { + final JPanel labelsPanel = new JPanel(new GridLayout(0, 1)); + labelsPanel.setBorder(JBUI.Borders.empty()); // Use padding on individual labels if needed + + for (String text : labels) { + final JBLabel label = new JBLabel(text, SwingConstants.CENTER); + label.setForeground(UIUtil.getLabelDisabledForeground()); + // Add padding to each label for spacing + label.setBorder(JBUI.Borders.empty(2, 0)); + labelsPanel.add(label); + } + + // Use VerticalFlowLayout to center the block of labels vertically + final JPanel centerPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.CENTER)); + centerPanel.add(labelsPanel); + + replacePanelLabel(toolWindow, centerPanel); + } + + + public void presentClickableLabel(ToolWindow toolWindow, List<LabelInput> labels) { final JPanel panel = new JPanel(new GridLayout(0, 1));