123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import 'package:flutter/material.dart';
- import 'package:photos/ente_theme_data.dart';
- import 'package:photos/ui/common/loading_widget.dart';
- import 'package:photos/utils/debouncer.dart';
- enum ExecutionState {
- idle,
- inProgress,
- successful,
- }
- typedef FutureVoidCallback = Future<void> Function();
- typedef BoolCallBack = bool Function();
- class ToggleSwitchWidget extends StatefulWidget {
- final BoolCallBack value;
- final FutureVoidCallback onChanged;
- const ToggleSwitchWidget({
- required this.value,
- required this.onChanged,
- Key? key,
- }) : super(key: key);
- @override
- State<ToggleSwitchWidget> createState() => _ToggleSwitchWidgetState();
- }
- class _ToggleSwitchWidgetState extends State<ToggleSwitchWidget> {
- bool? toggleValue;
- ExecutionState executionState = ExecutionState.idle;
- final _debouncer = Debouncer(const Duration(milliseconds: 300));
- @override
- void initState() {
- toggleValue = widget.value.call();
- super.initState();
- }
- @override
- Widget build(BuildContext context) {
- final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
- final Widget stateIcon = _stateIcon(enteColorScheme);
- return Row(
- children: [
- Padding(
- padding: const EdgeInsets.only(right: 2),
- child: AnimatedSwitcher(
- duration: const Duration(milliseconds: 175),
- switchInCurve: Curves.easeInExpo,
- switchOutCurve: Curves.easeOutExpo,
- child: stateIcon,
- ),
- ),
- SizedBox(
- height: 31,
- child: FittedBox(
- fit: BoxFit.contain,
- child: Switch.adaptive(
- activeColor: enteColorScheme.primary400,
- inactiveTrackColor: enteColorScheme.fillMuted,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- value: toggleValue ?? false,
- onChanged: (negationOfToggleValue) async {
- setState(() {
- toggleValue = negationOfToggleValue;
- //start showing inProgress statu icons if toggle takes more than debounce time
- _debouncer.run(
- () => Future(
- () {
- setState(() {
- executionState = ExecutionState.inProgress;
- });
- },
- ),
- );
- });
- final Stopwatch stopwatch = Stopwatch()..start();
- await widget.onChanged.call().onError(
- (error, stackTrace) => _debouncer.cancelDebounce(),
- );
- //for toggle feedback on short unsuccessful onChanged
- await _feedbackOnUnsuccessfulToggle(stopwatch);
- //debouncer gets canceled if onChanged takes less than debounce time
- _debouncer.cancelDebounce();
- final newValue = widget.value.call();
- setState(() {
- if (toggleValue == newValue) {
- if (executionState == ExecutionState.inProgress) {
- executionState = ExecutionState.successful;
- Future.delayed(const Duration(seconds: 2), () {
- setState(() {
- executionState = ExecutionState.idle;
- });
- });
- }
- } else {
- toggleValue = !toggleValue!;
- executionState = ExecutionState.idle;
- }
- });
- },
- ),
- ),
- ),
- ],
- );
- }
- Widget _stateIcon(enteColorScheme) {
- if (executionState == ExecutionState.idle) {
- return const SizedBox(width: 24);
- } else if (executionState == ExecutionState.inProgress) {
- return EnteLoadingWidget(
- color: enteColorScheme.strokeMuted,
- );
- } else if (executionState == ExecutionState.successful) {
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 1),
- child: Icon(
- Icons.check_outlined,
- size: 22,
- color: enteColorScheme.primary500,
- ),
- );
- } else {
- return const SizedBox(width: 24);
- }
- }
- Future<void> _feedbackOnUnsuccessfulToggle(Stopwatch stopwatch) async {
- final timeElapsed = stopwatch.elapsedMilliseconds;
- if (timeElapsed < 200) {
- await Future.delayed(
- Duration(milliseconds: 200 - timeElapsed),
- );
- }
- }
- }
|