diff --git a/lib/ui/podcast/podcast_details.dart b/lib/ui/podcast/podcast_details.dart index 2544e488..0559f80a 100644 --- a/lib/ui/podcast/podcast_details.dart +++ b/lib/ui/podcast/podcast_details.dart @@ -626,77 +626,85 @@ class FollowButton extends StatelessWidget { return StreamBuilder>( stream: bloc.details, builder: (context, snapshot) { + var ready = false; + var subscribed = false; + + // To prevent jumpy UI, we always need to display the follow/unfollow button. + // Display a disabled follow button until the full state it loaded. if (snapshot.hasData) { final state = snapshot.data; if (state is BlocPopulatedState) { - var p = state.results!; - - return Semantics( - liveRegion: true, - child: p.subscribed - ? OutlinedButton.icon( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), - ), - icon: const Icon( - Icons.delete_outline, - ), - label: Text(L.of(context)!.unsubscribe_label), - onPressed: () { - showPlatformDialog( - context: context, - useRootNavigator: false, - builder: (_) => BasicDialogAlert( - title: Text(L.of(context)!.unsubscribe_label), - content: Text(L.of(context)!.unsubscribe_message), - actions: [ - BasicDialogAction( - title: ExcludeSemantics( - child: ActionText( - L.of(context)!.cancel_button_label, + ready = true; + subscribed = state.results!.subscribed; + } + } + return Semantics( + liveRegion: true, + child: subscribed + ? OutlinedButton.icon( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), + ), + icon: const Icon( + Icons.delete_outline, + ), + label: Text(L.of(context)!.unsubscribe_label), + onPressed: ready + ? () { + showPlatformDialog( + context: context, + useRootNavigator: false, + builder: (_) => BasicDialogAlert( + title: Text(L.of(context)!.unsubscribe_label), + content: Text(L.of(context)!.unsubscribe_message), + actions: [ + BasicDialogAction( + title: ExcludeSemantics( + child: ActionText( + L.of(context)!.cancel_button_label, + ), ), + onPressed: () { + Navigator.pop(context); + }, ), - onPressed: () { - Navigator.pop(context); - }, - ), - BasicDialogAction( - title: ExcludeSemantics( - child: ActionText( - L.of(context)!.unsubscribe_button_label, + BasicDialogAction( + title: ExcludeSemantics( + child: ActionText( + L.of(context)!.unsubscribe_button_label, + ), ), + iosIsDefaultAction: true, + iosIsDestructiveAction: true, + onPressed: () { + bloc.podcastEvent(PodcastEvent.unsubscribe); + + Navigator.pop(context); + Navigator.pop(context); + }, ), - iosIsDefaultAction: true, - iosIsDestructiveAction: true, - onPressed: () { - bloc.podcastEvent(PodcastEvent.unsubscribe); - - Navigator.pop(context); - Navigator.pop(context); - }, - ), - ], - ), - ); - }, - ) - : OutlinedButton.icon( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), - ), - icon: const Icon( - Icons.add, - ), - label: Text(L.of(context)!.subscribe_label), - onPressed: () { - bloc.podcastEvent(PodcastEvent.subscribe); - }, - ), - ); - } - } - return Container(); + ], + ), + ); + } + : null, + ) + : OutlinedButton.icon( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8.0)), + ), + icon: const Icon( + Icons.add, + ), + label: Text(L.of(context)!.subscribe_label), + onPressed: ready + ? () { + bloc.podcastEvent(PodcastEvent.subscribe); + } + : null, + ), + ); }); } } @@ -713,18 +721,19 @@ class FilterButton extends StatelessWidget { return StreamBuilder>( stream: bloc.details, builder: (context, snapshot) { + Podcast? podcast; + if (snapshot.hasData) { final state = snapshot.data; if (state is BlocPopulatedState) { - var p = state.results!; - - return EpisodeFilterSelectorWidget( - podcast: p, - ); + podcast = state.results!; } } - return Container(); + + return EpisodeFilterSelectorWidget( + podcast: podcast, + ); }); } } @@ -741,18 +750,19 @@ class SortButton extends StatelessWidget { return StreamBuilder>( stream: bloc.details, builder: (context, snapshot) { + Podcast? podcast; + if (snapshot.hasData) { final state = snapshot.data; if (state is BlocPopulatedState) { - var p = state.results!; - - return EpisodeSortSelectorWidget( - podcast: p, - ); + podcast = state.results!; } } - return Container(); + + return EpisodeSortSelectorWidget( + podcast: podcast, + ); }); } } diff --git a/lib/ui/widgets/episode_filter_selector.dart b/lib/ui/widgets/episode_filter_selector.dart index eb349e0f..809196cd 100644 --- a/lib/ui/widgets/episode_filter_selector.dart +++ b/lib/ui/widgets/episode_filter_selector.dart @@ -12,7 +12,7 @@ import 'package:provider/provider.dart'; /// This widget allows the user to filter the episodes. class EpisodeFilterSelectorWidget extends StatefulWidget { - final Podcast podcast; + final Podcast? podcast; const EpisodeFilterSelectorWidget({ required this.podcast, @@ -44,12 +44,12 @@ class _EpisodeFilterSelectorWidgetState extends State( isScrollControlled: true, @@ -63,7 +63,7 @@ class _EpisodeFilterSelectorWidgetState extends State { Icons.sort, semanticLabel: L.of(context)!.episode_sort_semantic_label, ), - onPressed: widget.podcast.subscribed + onPressed: widget.podcast != null && widget.podcast!.subscribed ? () { showModalBottomSheet( isScrollControlled: true, @@ -61,7 +61,7 @@ class _EpisodeSortSelectorWidgetState extends State { ), builder: (context) { return EpisodeSortSlider( - podcast: widget.podcast, + podcast: widget.podcast!, ); }); }