<commit_before>// Copyright 2021 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.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../../../analytics/analytics.dart' as ga;
import '../../../../analytics/constants.dart' as analytics_constants;
import '../../../../primitives/utils.dart';
import '../../../../service/service_extensions.dart' as extensions;
import '../../../../shared/common_widgets.dart';
import '../../../../shared/theme.dart';
import '../../performance_controller.dart';
import '../../performance_utils.dart';
import '../controls/enhance_tracing/enhance_tracing.dart';
import '../controls/enhance_tracing/enhance_tracing_controller.dart';
import '../controls/enhance_tracing/enhance_tracing_model.dart';
import 'frame_analysis_model.dart';

class FrameHints extends StatelessWidget {
  const FrameHints({
    Key? key,
    required this.frameAnalysis,
    required this.enhanceTracingController,
  }) : super(key: key);

  final FrameAnalysis frameAnalysis;

  final EnhanceTracingController enhanceTracingController;

  @override
  Widget build(BuildContext context) {
    final performanceController = Provider.of<PerformanceController>(context);
    final frame = frameAnalysis.frame;
    final displayRefreshRate =
        performanceController.flutterFramesController.displayRefreshRate.value;
    final showUiJankHints = frame.isUiJanky(displayRefreshRate);
    final showRasterJankHints = frame.isRasterJanky(displayRefreshRate);
    if (!(showUiJankHints || showRasterJankHints)) {
      return const Text('No suggestions for this frame - no jank detected.');
    }

    final saveLayerCount = frameAnalysis.saveLayerCount;
    final intrinsicOperationsCount = frameAnalysis.intrinsicOperationsCount;

    final uiHints = showUiJankHints
        ? [
            const Text('UI Jank Detected'),
            const SizedBox(height: denseSpacing),
            EnhanceTracingHint(
              longestPhase: frameAnalysis.longestUiPhase,
              enhanceTracingState: frameAnalysis.frame.enhanceTracingState,
              enhanceTracingController: enhanceTracingController,
            ),
            const SizedBox(height: densePadding),
            if (intrinsicOperationsCount > 0)
              IntrinsicOperationsHint(intrinsicOperationsCount),
          ]
        : [];
    final rasterHints = showRasterJankHints
        ? [
            const Text('Raster Jank Detected'),
            const SizedBox(height: denseSpacing),
            if (saveLayerCount > 0) CanvasSaveLayerHint(saveLayerCount),
            const SizedBox(height: denseSpacing),
            if (frame.hasShaderTime)
              ShaderCompilationHint(shaderTime: frame.shaderDuration),
            const SizedBox(height: denseSpacing),
            const RasterStatsHint(),
          ]
        : [];

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        ...uiHints,
        if (showUiJankHints && showRasterJankHints)
          const SizedBox(height: defaultSpacing),
        ...rasterHints,
      ],
    );
  }
}

class _Hint extends StatelessWidget {
  const _Hint({Key? key, required this.message}) : super(key: key);

  final Widget message;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Icon(
          Icons.lightbulb_outline,
          size: defaultIconSize,
        ),
        const SizedBox(width: denseSpacing),
        Expanded(child: message),
      ],
    );
  }
}

@visibleForTesting
class EnhanceTracingHint extends StatelessWidget {
  const EnhanceTracingHint({
    Key? key,
    required this.longestPhase,
    required this.enhanceTracingState,
    required this.enhanceTracingController,
  }) : super(key: key);

  /// The longest [FramePhase] for the [FlutterFrame] this hint is for.
  final FramePhase longestPhase;

  /// The [EnhanceTracingState] that was active while drawing the [FlutterFrame]
  /// that this hint is for.
  final EnhanceTracingState? enhanceTracingState;

  final EnhanceTracingController enhanceTracingController;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return _Hint(
      message: RichText(
        maxLines: 2,
        text: TextSpan(
          text: '',
          children: [
            TextSpan(
              text: longestPhase.title,
              style: theme.fixedFontStyle,
            ),
            TextSpan(
              text: ' was the longest UI phase in this frame. ',
              style: theme.regularTextStyle,
            ),
            ..._hintForPhase(longestPhase, theme),
          ],
        ),
      ),
    );
  }

  List<InlineSpan> _hintForPhase(
    FramePhase phase,
    ThemeData theme,
  ) {
    final phaseType = phase.type;
    // TODO(kenz): when [enhanceTracingState] is not available, use heuristics
    // to detect whether tracing was enhanced for a frame (e.g. the depth or
    // quanity of child events under build / layout / paint).
    final tracingEnhanced =
        enhanceTracingState?.enhancedFor(phaseType) ?? false;
    switch (phaseType) {
      case FramePhaseType.build:
        return _enhanceTracingHint(
          settingTitle: extensions.profileWidgetBuilds.title,
          eventDescription: 'widget built',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      case FramePhaseType.layout:
        return _enhanceTracingHint(
          settingTitle: extensions.profileRenderObjectLayouts.title,
          eventDescription: 'render object laid out',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      case FramePhaseType.paint:
        return _enhanceTracingHint(
          settingTitle: extensions.profileRenderObjectPaints.title,
          eventDescription: 'render object painted',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      default:
        return [];
    }
  }

  List<InlineSpan> _enhanceTracingHint({
    required String settingTitle,
    required String eventDescription,
    required bool tracingEnhanced,
    required ThemeData theme,
  }) {
    if (tracingEnhanced) {
      return [
        TextSpan(
          text: 'Since "$settingTitle" was enabled while this frame was drawn, '
              'you should be able to see timeline events for each '
              '$eventDescription.',
        ),
      ];
    }
    final enhanceTracingButton = WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
        child: SmallEnhanceTracingButton(
          enhanceTracingController: enhanceTracingController,
        ),
      ),
    );
    return [<commit_msg>Add missing text style in the Frame Analysis frame hints (#4805)<commit_after>// Copyright 2021 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.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../../../../analytics/analytics.dart' as ga;
import '../../../../analytics/constants.dart' as analytics_constants;
import '../../../../primitives/utils.dart';
import '../../../../service/service_extensions.dart' as extensions;
import '../../../../shared/common_widgets.dart';
import '../../../../shared/theme.dart';
import '../../performance_controller.dart';
import '../../performance_utils.dart';
import '../controls/enhance_tracing/enhance_tracing.dart';
import '../controls/enhance_tracing/enhance_tracing_controller.dart';
import '../controls/enhance_tracing/enhance_tracing_model.dart';
import 'frame_analysis_model.dart';

class FrameHints extends StatelessWidget {
  const FrameHints({
    Key? key,
    required this.frameAnalysis,
    required this.enhanceTracingController,
  }) : super(key: key);

  final FrameAnalysis frameAnalysis;

  final EnhanceTracingController enhanceTracingController;

  @override
  Widget build(BuildContext context) {
    final performanceController = Provider.of<PerformanceController>(context);
    final frame = frameAnalysis.frame;
    final displayRefreshRate =
        performanceController.flutterFramesController.displayRefreshRate.value;
    final showUiJankHints = frame.isUiJanky(displayRefreshRate);
    final showRasterJankHints = frame.isRasterJanky(displayRefreshRate);
    if (!(showUiJankHints || showRasterJankHints)) {
      return const Text('No suggestions for this frame - no jank detected.');
    }

    final saveLayerCount = frameAnalysis.saveLayerCount;
    final intrinsicOperationsCount = frameAnalysis.intrinsicOperationsCount;

    final uiHints = showUiJankHints
        ? [
            const Text('UI Jank Detected'),
            const SizedBox(height: denseSpacing),
            EnhanceTracingHint(
              longestPhase: frameAnalysis.longestUiPhase,
              enhanceTracingState: frameAnalysis.frame.enhanceTracingState,
              enhanceTracingController: enhanceTracingController,
            ),
            const SizedBox(height: densePadding),
            if (intrinsicOperationsCount > 0)
              IntrinsicOperationsHint(intrinsicOperationsCount),
          ]
        : [];
    final rasterHints = showRasterJankHints
        ? [
            const Text('Raster Jank Detected'),
            const SizedBox(height: denseSpacing),
            if (saveLayerCount > 0) CanvasSaveLayerHint(saveLayerCount),
            const SizedBox(height: denseSpacing),
            if (frame.hasShaderTime)
              ShaderCompilationHint(shaderTime: frame.shaderDuration),
            const SizedBox(height: denseSpacing),
            const RasterStatsHint(),
          ]
        : [];

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        ...uiHints,
        if (showUiJankHints && showRasterJankHints)
          const SizedBox(height: defaultSpacing),
        ...rasterHints,
      ],
    );
  }
}

class _Hint extends StatelessWidget {
  const _Hint({Key? key, required this.message}) : super(key: key);

  final Widget message;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Icon(
          Icons.lightbulb_outline,
          size: defaultIconSize,
        ),
        const SizedBox(width: denseSpacing),
        Expanded(child: message),
      ],
    );
  }
}

@visibleForTesting
class EnhanceTracingHint extends StatelessWidget {
  const EnhanceTracingHint({
    Key? key,
    required this.longestPhase,
    required this.enhanceTracingState,
    required this.enhanceTracingController,
  }) : super(key: key);

  /// The longest [FramePhase] for the [FlutterFrame] this hint is for.
  final FramePhase longestPhase;

  /// The [EnhanceTracingState] that was active while drawing the [FlutterFrame]
  /// that this hint is for.
  final EnhanceTracingState? enhanceTracingState;

  final EnhanceTracingController enhanceTracingController;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return _Hint(
      message: RichText(
        maxLines: 2,
        text: TextSpan(
          text: '',
          children: [
            TextSpan(
              text: longestPhase.title,
              style: theme.fixedFontStyle,
            ),
            TextSpan(
              text: ' was the longest UI phase in this frame. ',
              style: theme.regularTextStyle,
            ),
            ..._hintForPhase(longestPhase, theme),
          ],
        ),
      ),
    );
  }

  List<InlineSpan> _hintForPhase(
    FramePhase phase,
    ThemeData theme,
  ) {
    final phaseType = phase.type;
    // TODO(kenz): when [enhanceTracingState] is not available, use heuristics
    // to detect whether tracing was enhanced for a frame (e.g. the depth or
    // quanity of child events under build / layout / paint).
    final tracingEnhanced =
        enhanceTracingState?.enhancedFor(phaseType) ?? false;
    switch (phaseType) {
      case FramePhaseType.build:
        return _enhanceTracingHint(
          settingTitle: extensions.profileWidgetBuilds.title,
          eventDescription: 'widget built',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      case FramePhaseType.layout:
        return _enhanceTracingHint(
          settingTitle: extensions.profileRenderObjectLayouts.title,
          eventDescription: 'render object laid out',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      case FramePhaseType.paint:
        return _enhanceTracingHint(
          settingTitle: extensions.profileRenderObjectPaints.title,
          eventDescription: 'render object painted',
          tracingEnhanced: tracingEnhanced,
          theme: theme,
        );
      default:
        return [];
    }
  }

  List<InlineSpan> _enhanceTracingHint({
    required String settingTitle,
    required String eventDescription,
    required bool tracingEnhanced,
    required ThemeData theme,
  }) {
    if (tracingEnhanced) {
      return [
        TextSpan(
          text: 'Since "$settingTitle" was enabled while this frame was drawn, '
              'you should be able to see timeline events for each '
              '$eventDescription.',
          style: theme.regularTextStyle,
        ),
      ];
    }
    final enhanceTracingButton = WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
        child: SmallEnhanceTracingButton(
          enhanceTracingController: enhanceTracingController,
        ),
      ),
    );
    return [
{'fraction_non_alphanumeric': 0.09512089184884899, 'fraction_numerical': 0.0013030259157376576, 'mean_word_length': 2.60140771637122, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6545385', 'n_tokens_mistral': 3758, 'n_tokens_neox': 3729, 'n_words': 896}