Skip to content

CircularCountDownTimer animation runs faster than real-time on specific devices (e.g.,Honor Magic 6, MagicOS 8.0) #70

Description

@Fifate

I'm experiencing an issue with the CircularCountDownTimer widget in my Flutter application. The animation displaying the countdown appears to run significantly faster than real-time only on certain devices, such as devices running MagicOS 6. On emulators, the timer functions correctly.

Steps to Reproduce:

Run the application on a device running MagicOS 6 (or the device exhibiting the issue).
Start the CircularCountDownTimer.
Observe the countdown animation. It will be noticeably faster than a real-time second.
Expected Behavior:

The CircularCountDownTimer animation should accurately reflect real-time passage, with each second in the animation corresponding to one actual second.

Actual Behavior:

The countdown animation runs faster than real-time.

Here is my code:

class TimerPage extends StatefulWidget {
  const TimerPage({super.key});

  @override
  TimerPageState createState() => TimerPageState();
}

class TimerPageState extends State<TimerPage> {
  final CountDownController _controller = CountDownController();
  bool isPause = true;
  bool isStarted = false;
  bool isBreakTime = false;
  String currentTask = '';  
  
  int _getCurrentDuration(SettingsProvider settings) {
    return (isBreakTime ? settings.breakDuration : settings.focusDuration).toInt() * 60;
  }

  String _timeFormatter(
    Function(Duration) defaultFormatter, 
    Duration duration) {
  if (duration.inMinutes >= 60) {
    return '${duration.inHours.toString().padLeft(2,'0')}:'
           '${(duration.inMinutes%60).toString().padLeft(2,'0')}:'
           '${(duration.inSeconds%60).toString().padLeft(2,'0')}';
  } else {
    return '${duration.inMinutes.toString().padLeft(2,'0')}:'
           '${(duration.inSeconds%60).toString().padLeft(2,'0')}';
  }
}

  @override
  Widget build(BuildContext context) {
    return Consumer<SettingsProvider>(
      builder: (context, settings, child) {
        final theme = Theme.of(context);
        final iconColor = theme.textTheme.bodyMedium!.color;
        
        return Center(
          child: Column(
            children: [
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: TextFormField(
                  style: TextStyle(color: theme.textTheme.bodyMedium!.color),
                  decoration: InputDecoration(
                    labelText: 'Input Task',
                    border: OutlineInputBorder(),
                  ),
                  onChanged: (value) {
                    setState(() {
                      currentTask = value;
                    });
                  },
                ),
              ),

              CircularCountDownTimer(
                duration: _getCurrentDuration(settings),
                controller: _controller,
                width: MediaQuery.of(context).size.width / 2,
                height: MediaQuery.of(context).size.height / 2,
                ringColor: theme.cardColor,
                fillColor: Colors.transparent,
                fillGradient: LinearGradient(
                  colors: [theme.colorScheme.primary, theme.colorScheme.secondary],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                ),
                backgroundColor: theme.scaffoldBackgroundColor,
                strokeWidth: 20.0,
                strokeCap: StrokeCap.round,
                textStyle: TextStyle(
                  fontSize: 33.0,
                  color: theme.textTheme.bodyMedium!.color,
                  fontWeight: FontWeight.bold,
                ),
                isReverse: true,
                isReverseAnimation: false,
                isTimerTextShown: true,
                autoStart: false,
                timeFormatterFunction: _timeFormatter,
                onStart: () {
                  debugPrint('Countdown Started');
                },
                onComplete: () {
                  _handleTimerComplete();
                },
                onChange: (String timeStamp) {
                  debugPrint('Countdown Changed $timeStamp');
                },
              ),

              Padding(
                padding: const EdgeInsets.symmetric(vertical: 16.0),
                child: Column(
                  children: [
                    Text(
                      isBreakTime ? "Break" : "Focus",
                      style: TextStyle(
                        fontSize: 20,
                        color: theme.textTheme.bodyMedium!.color,
                      ),
                    ),
                    if (currentTask.isNotEmpty)
                      Padding(
                        padding: const EdgeInsets.only(top: 8.0),
                        child: Text(
                          currentTask,
                          style: TextStyle(
                            fontSize: 16,
                            color: theme.textTheme.bodyMedium!.color?.withOpacity(0.7),
                          ),
                        ),
                      ),
                  ],
                ),
              ),

              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  IconButton(
                    color: iconColor,
                    onPressed: _handlePlayPause,
                    icon: Icon(isPause ? Icons.play_arrow : Icons.pause),
                  ),

                  IconButton(
                    color: iconColor,
                    onPressed: _handleReset,
                    icon: Icon(Icons.restart_alt),
                  ),

                  IconButton(
                    color: iconColor,
                    onPressed: _handleSkip,
                    icon: Icon(Icons.skip_next),
                  )
                ],
              )
            ],
          ),
        );
      },
    );
  }

  void _switchToNextPhase() {
    final settings = Provider.of<SettingsProvider>(context, listen: false);
    setState(() {
      isBreakTime = !isBreakTime;
      isStarted = true;
      isPause = false;
    });
    
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _controller.restart(duration: _getCurrentDuration(settings));
    });
  }

  void _handleTimerComplete() {
    debugPrint('Timer Complete');
    _switchToNextPhase();
  }

  void _handlePlayPause() {
    setState(() {
      if (!isStarted) {
        _controller.start();
        isStarted = true;
        isPause = false;
        return;
      }
      
      if (isPause) {
        _controller.resume();
      } else {
        _controller.pause();
      }
      isPause = !isPause;
    });
  }

  void _handleReset() {
    setState(() {
      isPause = true;
      isStarted = false;
    });
    
    final settings = Provider.of<SettingsProvider>(context, listen: false);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _controller.restart(duration: _getCurrentDuration(settings));
      _controller.pause();
    });
  }

  void _handleSkip() {
    _switchToNextPhase();
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions