diff --git a/plugins/ide-features-trainer/src/training/featuresSuggester/FeatureSuggestersManager.kt b/plugins/ide-features-trainer/src/training/featuresSuggester/FeatureSuggestersManager.kt index 8ebdc802f9c2..0c080eee7110 100644 --- a/plugins/ide-features-trainer/src/training/featuresSuggester/FeatureSuggestersManager.kt +++ b/plugins/ide-features-trainer/src/training/featuresSuggester/FeatureSuggestersManager.kt @@ -11,6 +11,7 @@ import com.intellij.openapi.editor.ex.EditorEventMulticasterEx import com.intellij.openapi.editor.ex.FocusChangeListener import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project +import kotlinx.coroutines.CoroutineScope import training.featuresSuggester.actions.Action import training.featuresSuggester.actions.EditorFocusGainedAction import training.featuresSuggester.settings.FeatureSuggesterSettings @@ -19,7 +20,7 @@ import training.featuresSuggester.ui.NotificationSuggestionPresenter import training.featuresSuggester.ui.SuggestionPresenter @Service(Service.Level.PROJECT) -internal class FeatureSuggestersManager(private val project: Project) : Disposable { +internal class FeatureSuggestersManager(private val project: Project, private val coroutineScope: CoroutineScope) : Disposable { private val suggestionPresenter: SuggestionPresenter = NotificationSuggestionPresenter() init { @@ -56,7 +57,7 @@ internal class FeatureSuggestersManager(private val project: Project) : Disposab if (suggestion is PopupSuggestion) { suggester.logStatisticsThatSuggestionIsFound(suggestion) if (suggester.isEnabled() && (SuggestingUtils.forceShowSuggestions || suggester.isSuggestionNeeded())) { - suggestionPresenter.showSuggestion(project, suggestion, disposable = this) + suggestionPresenter.showSuggestion(project, suggestion, coroutineScope = coroutineScope) fireSuggestionFound(suggestion) FeatureSuggesterSettings.instance().updateSuggestionShownTime(suggestion.suggesterId) } diff --git a/plugins/ide-features-trainer/src/training/featuresSuggester/ui/SuggestionPresenter.kt b/plugins/ide-features-trainer/src/training/featuresSuggester/ui/SuggestionPresenter.kt index 489a855364a5..2d970d013551 100644 --- a/plugins/ide-features-trainer/src/training/featuresSuggester/ui/SuggestionPresenter.kt +++ b/plugins/ide-features-trainer/src/training/featuresSuggester/ui/SuggestionPresenter.kt @@ -8,16 +8,16 @@ import com.intellij.notification.Notification import com.intellij.notification.NotificationGroup import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType -import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.application.EDT import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.components.ComponentManagerEx +import com.intellij.openapi.application.asContextElement +import com.intellij.openapi.components.serviceAsync import com.intellij.openapi.project.Project -import com.intellij.util.Alarm -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import training.featuresSuggester.DocumentationSuggestion import training.featuresSuggester.FeatureSuggesterBundle import training.featuresSuggester.PopupSuggestion @@ -26,7 +26,7 @@ import training.featuresSuggester.settings.FeatureSuggesterSettings import training.featuresSuggester.statistics.FeatureSuggesterStatistics internal interface SuggestionPresenter { - fun showSuggestion(project: Project, suggestion: PopupSuggestion, disposable: Disposable) + fun showSuggestion(project: Project, suggestion: PopupSuggestion, coroutineScope: CoroutineScope) } @Suppress("DialogTitleCapitalization") @@ -34,34 +34,42 @@ internal class NotificationSuggestionPresenter : SuggestionPresenter { private val notificationGroup: NotificationGroup = NotificationGroupManager.getInstance() .getNotificationGroup("IDE Feature Suggester") - override fun showSuggestion(project: Project, suggestion: PopupSuggestion, disposable: Disposable) { + override fun showSuggestion(project: Project, suggestion: PopupSuggestion, coroutineScope: CoroutineScope) { val notification = notificationGroup.createNotification( title = FeatureSuggesterBundle.message("notification.title"), content = suggestion.message, type = NotificationType.INFORMATION ) + val expireJob = coroutineScope.launch(Dispatchers.EDT + ModalityState.any().asContextElement(), start = CoroutineStart.LAZY) { + if (!notification.isExpired) { + delay(10_000) + notification.expire() + } + } + when (suggestion) { is TipSuggestion -> { - val action = createShowTipAction(project, notification, suggestion) + val action = createShowTipAction(project, notification, suggestion, coroutineScope, expireJob) if (action != null) { notification.addAction(action) } } is DocumentationSuggestion -> { - notification.addAction(createGoToDocumentationAction(notification, suggestion)) + notification.addAction(createGoToDocumentationAction(notification, suggestion, expireJob)) } } - notification.addAction(createDontSuggestAction(notification, suggestion)) + notification.addAction(createDontSuggestAction(notification, suggestion, expireJob)) notification.notify(project) - Alarm(disposable).addRequest(notification::expire, 10000, ModalityState.any()) + expireJob.start() FeatureSuggesterStatistics.logNotificationShowed(suggestion.suggesterId) } - private fun createDontSuggestAction(notification: Notification, suggestion: PopupSuggestion): AnAction { + private fun createDontSuggestAction(notification: Notification, suggestion: PopupSuggestion, expireJob: Job): AnAction { return object : AnAction(FeatureSuggesterBundle.message("notification.dont.suggest")) { override fun actionPerformed(e: AnActionEvent) { + expireJob.cancel() val settings = FeatureSuggesterSettings.instance() settings.setEnabled(suggesterId = suggestion.suggesterId, enabled = false) notification.hideBalloon() @@ -70,7 +78,7 @@ internal class NotificationSuggestionPresenter : SuggestionPresenter { } } - private fun createGoToDocumentationAction(notification: Notification, suggestion: DocumentationSuggestion): AnAction { + private fun createGoToDocumentationAction(notification: Notification, suggestion: DocumentationSuggestion, expireJob: Job): AnAction { return object : AnAction( FeatureSuggesterBundle.message( "notification.open.help", @@ -78,6 +86,7 @@ internal class NotificationSuggestionPresenter : SuggestionPresenter { ) ) { override fun actionPerformed(e: AnActionEvent) { + expireJob.cancel() BrowserUtil.open(suggestion.documentURL) notification.hideBalloon() FeatureSuggesterStatistics.logNotificationLearnMore(suggestion.suggesterId) @@ -85,14 +94,22 @@ internal class NotificationSuggestionPresenter : SuggestionPresenter { } } - private fun createShowTipAction(project: Project, notification: Notification, suggestion: TipSuggestion): AnAction? { + private fun createShowTipAction( + project: Project, + notification: Notification, + suggestion: TipSuggestion, + coroutineScope: CoroutineScope, + expireJob: Job, + ): AnAction? { val tip = TipAndTrickBean.findById(suggestion.suggestingTipId) ?: return null return object : AnAction(FeatureSuggesterBundle.message("notification.learn.more")) { override fun actionPerformed(e: AnActionEvent) { - (project as ComponentManagerEx).getCoroutineScope().launch { - TipAndTrickManager.getInstance().showTipDialog(project, tip) - notification.hideBalloon() - FeatureSuggesterStatistics.logNotificationLearnMore(suggestion.suggesterId) + expireJob.cancel() + notification.hideBalloon() + FeatureSuggesterStatistics.logNotificationLearnMore(suggestion.suggesterId) + + coroutineScope.launch { + serviceAsync().showTipDialog(project, tip) } }