feat: Sync offline/online playback when able (#431)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-08-03 13:35:56 +02:00 committed by GitHub
parent 15ac3566e2
commit 092836328f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1002 additions and 497 deletions

View file

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
class AnimatedVisibility extends StatelessWidget {
final Widget? child;
final bool visible;
final Duration duration;
const AnimatedVisibility(
{required this.child, required this.visible, this.duration = const Duration(milliseconds: 250), super.key});
@override
Widget build(BuildContext context) {
return AnimatedOpacity(
duration: duration,
opacity: visible ? 1 : 0,
child: IgnorePointer(
ignoring: !visible,
child: SizedBox(
height: visible ? null : 16,
child: child,
),
),
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:flutter/material.dart' hide ConnectionState;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/providers/connectivity_provider.dart';
import 'package:fladder/util/localization_helper.dart';
class OfflineBanner extends ConsumerWidget {
const OfflineBanner({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isOffline = ref.watch(connectivityStatusProvider.select((value) => value == ConnectionState.offline));
final theme = Theme.of(context);
return AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: isOffline ? 1 : 0,
child: IgnorePointer(
child: Row(
spacing: 12,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
IconsaxPlusLinear.cloud_cross,
color: theme.colorScheme.onErrorContainer,
size: 20,
),
Text(
context.localized.offline,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onErrorContainer,
),
),
],
),
),
);
}
}

View file

@ -48,11 +48,11 @@ class _SelectableIconButtonState extends ConsumerState<SelectableIconButton> {
setState(() => loading = true);
try {
await widget.onPressed();
if (context.mounted) await context.refreshData();
} catch (e) {
log(e.toString());
} finally {
setState(() => loading = false);
if (context.mounted) await context.refreshData();
}
},
child: Padding(