UnicornsOnLSD
UnicornsOnLSD

Reputation: 773

Can I use Dismissible without actually dismissing the widget?

I'm trying to make a widget that can be swiped to change the currently playing song in a playlist. I'm trying to mimic how other apps do it by letting the user swipe away the current track and the next one coming in. Dismissible is so close to what I actually want. It has a nice animation and I can easily use the onDismissed function to handle the logic. My issue is that Dismissible actually wants to remove the widget from the tree, which I don't want.

The widget I'm swiping gets updated with a StreamBuilder when the song changes, so being able to swipe away the widget to a new one would be perfect. Can I do this or is there a better widget for my needs?

Here's the widget I'm working on:

class NowPlayingBar extends StatelessWidget {
  const NowPlayingBar({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<ScreenState>(
      stream: _screenStateStream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          final screenState = snapshot.data;
          final queue = screenState.queue;
          final mediaItem = screenState.mediaItem;
          final state = screenState.playbackState;
          final processingState =
              state?.processingState ?? AudioProcessingState.none;
          final playing = state?.playing ?? false;
          if (mediaItem != null) {
            return Container(
              width: MediaQuery.of(context).size.width,
              child: Dismissible(
                key: Key("NowPlayingBar"),
                onDismissed: (direction) {
                  switch (direction) {
                    case DismissDirection.startToEnd:
                      AudioService.skipToNext();
                      break;
                    case DismissDirection.endToStart:
                      AudioService.skipToPrevious();
                      break;
                    default:
                      throw ("Unsupported swipe direction ${direction.toString()} on NowPlayingBar!");
                  }
                },
                child: ListTile(
                  leading: AlbumImage(itemId: mediaItem.id),
                  title: mediaItem == null ? null : Text(mediaItem.title),
                  subtitle: mediaItem == null ? null : Text(mediaItem.album),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      if (playing)
                        IconButton(
                            onPressed: () => AudioService.pause(),
                            icon: Icon(Icons.pause))
                      else
                        IconButton(
                            onPressed: () => AudioService.play(),
                            icon: Icon(Icons.play_arrow)),
                    ],
                  ),
                ),
              ),
            );
          } else {
            return Container(
                width: MediaQuery.of(context).size.width,
                child: ListTile(
                  title: Text("Nothing playing..."),
                ));
          }
        } else {
          return Container(
              width: MediaQuery.of(context).size.width,
              // The child below looks pretty stupid but it's actually genius.
              // I wanted the NowPlayingBar to stay the same length when it doesn't have data
              // but I didn't want to actually use a ListTile to tell the user that.
              // I use a ListTile to create a box with the right height, and put whatever I want on top.
              // I could just make a container with the length of a ListTile, but that value could change in the future.
              child: Stack(
                alignment: Alignment.center,
                children: [
                  ListTile(),
                  Text(
                    "Nothing Playing...",
                    style: TextStyle(color: Colors.grey, fontSize: 18),
                  )
                ],
              ));
        }
      },
    );
  }
}

Here's the effect that I'm going for (although I want the whole ListTile to get swiped, not just the song name): https://i.imgur.com/ZapzpJS.mp4

Upvotes: 7

Views: 2536

Answers (1)

UnicornsOnLSD
UnicornsOnLSD

Reputation: 773

This can be done by using the confirmDismiss callback instead of the onDismiss callback. To make sure that the widget never actually gets dismissed, you need to return false at the end of the function.

Dismissible(
    confirmDismiss: (direction) {
        ...
        return false;
    }
)

Upvotes: 8

Related Questions