|  | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:async'; | 
|  |  | 
|  | import 'package:analysis_server/src/analysis_server.dart'; | 
|  | import 'package:analyzer/instrumentation/service.dart'; | 
|  | import 'package:unified_analytics/unified_analytics.dart'; | 
|  |  | 
|  | /// An interface for interacting with surveys via the unified_analytics package. | 
|  | class SurveyManager { | 
|  | /// The period to wait after startup before first checking for surveys. | 
|  | /// | 
|  | /// This delay avoids showing too many prompts at startup (the IDE may prompt | 
|  | /// to fetch packages and the server may prompt for analytics or "dart fix"). | 
|  | final Duration _initialDelay; | 
|  |  | 
|  | /// The period to wait between checks for surveys. | 
|  | /// | 
|  | /// We check periodically (instead of only once) so that users who keep their | 
|  | /// IDEs open for long periods will still have survey checks periodically. | 
|  | final Duration _checkFrequency; | 
|  |  | 
|  | /// The analytics object that provides access to surveys. | 
|  | final Analytics _analytics; | 
|  |  | 
|  | final InstrumentationService _instrumentationService; | 
|  |  | 
|  | /// The timer for triggering the next survey check. | 
|  | Timer? _timer; | 
|  |  | 
|  | /// The analysis server that can show prompts to the user. | 
|  | final AnalysisServer _server; | 
|  |  | 
|  | /// Tracks whether we've been asked to shutdown. | 
|  | /// | 
|  | /// This is used to prevent an in-progress async check from starting another | 
|  | /// timer if cancellation occurred while it was running. | 
|  | bool _isShutdown = false; | 
|  |  | 
|  | SurveyManager( | 
|  | this._server, | 
|  | this._instrumentationService, | 
|  | this._analytics, { | 
|  | // Delay the first check slightly because there are other prompts that | 
|  | // may appear at startup (fetching packages, analytics, "dart fix") that | 
|  | // we aren't coordinated with. | 
|  | Duration initialDelay = const Duration(minutes: 5), | 
|  | Duration checkFrequency = const Duration(hours: 24), | 
|  | }) : _initialDelay = initialDelay, | 
|  | _checkFrequency = checkFrequency { | 
|  | _timer = Timer(_initialDelay, checkForSurveys); | 
|  | } | 
|  |  | 
|  | Future<void> checkForSurveys() async { | 
|  | try { | 
|  | // Ensure we can prompt the user and open web pages. | 
|  | var prompt = _server.userPromptSender; | 
|  | var uriOpener = _server.openUriNotificationSender; | 
|  | if (prompt == null || uriOpener == null) return; | 
|  |  | 
|  | // Find the first survey to show. | 
|  | var surveys = await _analytics.fetchAvailableSurveys(); | 
|  | var survey = surveys.firstOrNull; | 
|  | if (survey == null) return; | 
|  |  | 
|  | // If we were shutdown during the above async request, skip any further | 
|  | // processing. We don't want to mark a survey as shown if we're shutting | 
|  | // down. | 
|  | if (_isShutdown) return; | 
|  |  | 
|  | // Create a map of buttons by text because we only get the button text | 
|  | // back and we need the button to read the URL and record the interaction. | 
|  | var buttonMap = { | 
|  | for (var button in survey.buttonList) button.buttonText: button, | 
|  | }; | 
|  |  | 
|  | _analytics.surveyShown(survey); | 
|  | var clickedButtonText = await prompt( | 
|  | MessageType.info, | 
|  | survey.description, | 
|  | buttonMap.keys.toList(), | 
|  | ); | 
|  | var clickedButton = buttonMap[clickedButtonText]; | 
|  | if (clickedButton == null) return; | 
|  |  | 
|  | // Record that ths survey was interacted with so it's not shown again. | 
|  | _analytics.surveyInteracted(survey: survey, surveyButton: clickedButton); | 
|  |  | 
|  | // If this button had a URL, open it. If not, it was probably a dismiss | 
|  | // or snooze button. | 
|  | var url = clickedButton.url; | 
|  | if (url != null) { | 
|  | await uriOpener(Uri.parse(url)); | 
|  | } | 
|  | } catch (e) { | 
|  | _instrumentationService.logError('Failed to perform survey checks: $e'); | 
|  | } finally { | 
|  | // Wait for the usual check period before checking again. | 
|  | if (!_isShutdown) { | 
|  | _timer = Timer(_checkFrequency, checkForSurveys); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void shutdown() { | 
|  | _isShutdown = true; | 
|  | _timer?.cancel(); | 
|  | } | 
|  | } |