Skip to content

[Impeller] Widget doesn't build properly unless wrapped with RepaintBoundary #127104

@AlexDochioiu

Description

@AlexDochioiu

Is there an existing issue for this?

Steps to reproduce

I attached the code below for the class that I use.

Expected results

Widget paints properly

Actual results

In the UPCOMING section, both rows are the exact same widget:

  1. First one is SliverToBoxAdapter -> RepaintBoundary -> LinearPercentIndicator
  2. Second one is SliverToBoxAdapter -> LinearPercentIndicator

The one that's not wrapped inside RepaintBoundary misses some of the text in the Reward section.

image

Code sample

Code sample
LinearPercentIndicator(
                duration: const Duration(milliseconds: 500),
                fill: LinearPercentIndicator_LineModel.gradient(
                        progressPercent: 0.5,
                        progressGradient: LinearGradient(colors: context.colorScheme.progressGradient),
                        onProgressColor: Colors.black,
                        glow: true,
                      ),
                contentBuilder: (context, color, child) => _LineContentWidget(
                  color: color,
                  date: upcomingReward!.dateTime,
                  ticker: "PSB",
                  showDateAsCountdown: true,
                  formattedRewardAmount: "2.335431,
                ),
              )
import 'package:core/export.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:nft_craze_wallet/config/size.dart';
import 'package:nft_craze_wallet/pages/home/wallet/wallet_page.dart';
import 'package:nft_craze_wallet/utils/color_extensions.dart';
import 'package:nft_craze_wallet/utils/ui/blur.dart';
import 'package:simple_animations/simple_animations.dart';

part 'linear_percent_indicator.freezed.dart';

@freezed
class LinearPercentIndicator_LineModel with _$LinearPercentIndicator_LineModel {
  const LinearPercentIndicator_LineModel._();

  const factory LinearPercentIndicator_LineModel.color({
    Color? progressColor,
    Color? onProgressColor,
    @Default(false) bool glow,
    required double progressPercent,
  }) = LinearPercentIndicator_LineModel_Color;

  const factory LinearPercentIndicator_LineModel.gradient({
    required Gradient progressGradient,
    Color? onProgressColor,
    @Default(false) bool glow,
    required double progressPercent,
  }) = LinearPercentIndicator_LineModel_Gradient;
}

class LinearPercentIndicator extends StatelessWidget {
  const LinearPercentIndicator({
    super.key,
    required this.duration,
    required this.fill,
    this.secondFill,
    required this.contentBuilder,
    this.backgroundColor,
    this.onBackgroundColor,
  });

  final Duration duration;

  final LinearPercentIndicator_LineModel fill;
  final LinearPercentIndicator_LineModel? secondFill;
  final ValueWidgetBuilder<Color> contentBuilder;
  final Color? backgroundColor;
  final Color? onBackgroundColor;

  @override
  Widget build(BuildContext context) {
    final maxWidth =
        ((MediaQuery.of(context).size.width - SizeConst.WINDOW_INSET * 2) / (secondFill == null ? 1 : 2.5)).atLeast(0);
    final percent = fill.progressPercent.isNaN ? 0.5 : fill.progressPercent;
    final secondPercent = secondFill == null
        ? null
        : secondFill!.progressPercent.isNaN
            ? 0.5
            : secondFill!.progressPercent;

    final leftChild = PlayAnimationBuilder(
      duration: duration,
      tween: Tween<double>(begin: 0, end: percent),
      builder: (context, value, child) => Container(
        width: maxWidth * value,
        height: SizeConst.SIZE_24x,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(10),
          gradient: fill.map(
            color: (c) => null,
            gradient: (g) => g.progressGradient,
          ),
          color: fill.map(
            color: (c) => c.progressColor ?? const Color(0xFF1A75FF),
            gradient: (g) => null,
          ),
        ),
      ),
    );

    final rightChild = secondFill == null
        ? null
        : PlayAnimationBuilder(
            duration: duration,
            tween: Tween<double>(begin: 0, end: secondPercent!),
            builder: (context, value, child) => Container(
              width: maxWidth * value,
              height: SizeConst.SIZE_24x,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(10),
                gradient: secondFill?.map(
                  color: (c) => null,
                  gradient: (g) => g.progressGradient,
                ),
                color: secondFill?.map(
                  color: (c) => c.progressColor ?? const Color(0xFF1A75FF),
                  gradient: (g) => null,
                ),
              ),
            ),
          );

    return Padding(
      padding: const EdgeInsets.only(
        bottom: SizeConst.SIZE_16x,
        left: SizeConst.WINDOW_INSET,
        right: SizeConst.WINDOW_INSET,
      ),
      child: Stack(
        alignment: Alignment.centerLeft,
        clipBehavior: Clip.none,
        children: [
          if (context.isDarkMode || backgroundColor != null)
            Container(
              height: SizeConst.SIZE_24x,
              width: double.infinity,
              clipBehavior: Clip.hardEdge,
              decoration: BoxDecoration(
                borderRadius: const BorderRadius.all(Radius.circular(10.0)),
                color: backgroundColor ?? context.colorScheme.progressBarBackground,
              ),
            ),
          contentBuilder(context, context.colorScheme.onProgressBarBackground, null),
          leftChild,
          Align(alignment: Alignment.topRight, child: rightChild),
          if (fill.glow) Blurred(tileMode: TileMode.decal, child: leftChild),
          if (secondFill?.glow == true && rightChild != null)
            Align(
              alignment: Alignment.topRight,
              child: Blurred(tileMode: TileMode.decal, child: rightChild),
            ),
          Align(
            alignment: Alignment.centerLeft,
            child: ClipRect(
              child: PlayAnimationBuilder(
                duration: duration,
                tween: Tween<double>(begin: 0, end: 1 / (secondPercent == null ? 1 : 2.5)),
                child: contentBuilder(context, fill.onProgressColor ?? Colors.white, null),
                builder: (context, value, child) => Align(
                  alignment: Alignment.centerLeft,
                  widthFactor: percent * value,
                  child: child,
                ),
              ),
            ),
          ),
          if (secondPercent != null)
            Align(
              alignment: Alignment.centerRight,
              child: ClipRect(
                child: PlayAnimationBuilder(
                  duration: duration,
                  tween: Tween<double>(begin: 0, end: secondPercent / 2.5),
                  child: contentBuilder(context, fill.onProgressColor ?? Colors.white, null),
                  builder: (context, value, child) => Align(
                    alignment: Alignment.centerRight,
                    widthFactor: value,
                    child: child,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

class _LineContentWidget extends StatelessWidget {
  const _LineContentWidget({
    Key? key,
    required this.color,
    required this.date,
    required this.showDateAsCountdown,
    required this.formattedRewardAmount,
    this.onRewardTextTapped,
    required this.ticker,
  }) : super(key: key);

  final Color color;
  final DateTime date;
  final bool showDateAsCountdown;

  final String formattedRewardAmount;
  final void Function(BuildContext context)? onRewardTextTapped;

  final String? ticker;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.maxFinite,
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: SizeConst.SIZE_12x + 2),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Expanded(
              child: showDateAsCountdown
                  ? RepaintBoundary(
                      child: CountdownTimer(
                        widgetBuilder: (context, time) => Text.rich(
                          TextSpan(
                            children: [
                              TextSpan(
                                text: context.loc.rewardsPageCountdownTimeLeft(_builderCountdownTimer(time, true)),
                                style:
                                    context.textTheme.labelSmall?.copyWith(color: color, fontWeight: FontWeight.w500),
                              ),
                            ],
                          ),
                        ),
                        endTime: date.millisecondsSinceEpoch,
                      ),
                    )
                  : Text(
                      DateFormat.yMMMd().format(date),
                      style: context.textTheme.labelSmall?.copyWith(color: color, fontWeight: FontWeight.w500),
                    ),
            ),
            GestureDetector(
              onTap: onRewardTextTapped != null ? () => onRewardTextTapped!(context) : null,
              child: DiscreteWidget(
                blurSigmaY: 3,
                child: Text.rich(
                  TextSpan(
                    children: [
                      TextSpan(text: '$formattedRewardAmount '),
                      Constants.ADA_SYMBOL,
                      if (onRewardTextTapped != null) ...[
                        const TextSpan(text: '  '),
                        const TextSpan(
                          children: [Constants.INFO_SYMBOL],
                          style: TextStyle(fontSize: 11),
                        ),
                      ]
                    ],
                  ),
                  style: context.textTheme.labelSmall?.copyWith(
                    color: onRewardTextTapped != null ? context.colorScheme.actionBlue : color,
                    fontWeight: onRewardTextTapped != null ? FontWeight.w700 : FontWeight.w500,
                  ),
                ),
              ),
            ),
            Expanded(
              child: Text(
                ticker ?? context.loc.rewardsPageRegisteredNALabel,
                textAlign: TextAlign.right,
                style: context.textTheme.labelSmall
                    ?.copyWith(color: color, fontStyle: FontStyle.italic, fontWeight: FontWeight.w700),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.10.1, on macOS 13.3.1 22E772610a darwin-arm64, locale en-GB)
    • Flutter version 3.10.1 on channel stable at /Users/alexandrudochioiu/.asdf/installs/flutter/3.10.1-stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision d3d8effc68 (35 hours ago), 2023-05-16 17:59:05 -0700
    • Engine revision b4fb11214d
    • Dart version 3.0.1
    • DevTools version 2.23.1

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    • Android SDK at /Users/alexandrudochioiu/Library/Android/sdk
    • Platform android-33, build-tools 33.0.1
    • ANDROID_HOME = /Users/alexandrudochioiu/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14E222b
    • CocoaPods version 1.12.0

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)

[✓] IntelliJ IDEA Community Edition (version 2022.3.2)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.78.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.64.0

[✓] Connected device (3 available)
    • iPhone 14 Pro (mobile) • A1A6A512-11DF-47F0-B0E6-30171868398B • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-4 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 13.3.1 22E772610a darwin-arm64
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 113.0.5672.92

[✓] Network resources
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work liste: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.found in release: 3.10Found to occur in 3.10found in release: 3.11Found to occur in 3.11has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-iosiOS applications specificallyr: solvedIssue is closed as solvedteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions