Skip to content

Commit

Permalink
feat!: rework stations to be identified by uuid, display search in a …
Browse files Browse the repository at this point in the history
…list
  • Loading branch information
Feichtmeier committed Sep 23, 2024
1 parent de89adc commit aa3ed98
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 127 deletions.
27 changes: 18 additions & 9 deletions lib/common/data/audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Audio {
/// The url of the image if remote.
final String? imageUrl;

/// The description of the audio file or stream.
/// The description of the audio file or stream, for radio stations this is the UUID
final String? description;

/// Website link or feed url in case of podcasts.
Expand All @@ -42,7 +42,7 @@ class Audio {
/// The album of the audio file or stream.
final String? album;

/// The album artist(s) of the audio file or stream.
/// The album artist(s) of the audio file or stream, for radio stations this is the codec.
final String? albumArtist;

/// The track number of the audio file or stream.
Expand All @@ -69,7 +69,7 @@ class Audio {
/// The image data. Only for local audio.
final Uint8List? pictureData;

/// The file size of the audio file.
/// The file size of the audio file, for radio stations this is the stream quality in kbps.
final int? fileSize;

/// Optional art that can belong to a parent element.
Expand Down Expand Up @@ -260,15 +260,22 @@ class Audio {
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is Audio &&
((other.url != null && other.url == url) ||
(other.path != null && other.path == path));
if (other is Audio) {
if (other.audioType != null &&
other.audioType == AudioType.radio &&
audioType == AudioType.radio) {
return other.description == description;
}

return (other.url != null && other.url == url) ||
(other.path != null && other.path == path);
}

return false;
}

@override
int get hashCode {
return path.hashCode ^ url.hashCode;
}
int get hashCode => path.hashCode ^ url.hashCode ^ description.hashCode;

factory Audio.fromMetadata({
required String path,
Expand Down Expand Up @@ -313,6 +320,8 @@ class Audio {
imageUrl: station.favicon,
website: station.homepage,
description: station.stationUUID,
fileSize: station.bitrate,
albumArtist: station.codec,
);
}

Expand Down
3 changes: 2 additions & 1 deletion lib/common/view/audio_page_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ enum AudioPageType {
artist,
likedAudio,
playlist,
album;
album,
radioSearch;
}
36 changes: 17 additions & 19 deletions lib/common/view/audio_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../../extensions/duration_x.dart';
import '../../extensions/theme_data_x.dart';
import '../../l10n/l10n.dart';
import '../../library/library_model.dart';
import '../../player/player_model.dart';
import '../data/audio.dart';
import 'audio_page_type.dart';
import 'audio_tile_image.dart';
Expand All @@ -20,13 +21,10 @@ class AudioTile extends StatefulWidget with WatchItStatefulWidgetMixin {
const AudioTile({
super.key,
required this.pageId,
required this.libraryModel,
required this.insertIntoQueue,
this.insertIntoQueue,
required this.selected,
required this.audio,
required this.isPlayerPlaying,
required this.pause,
required this.resume,
this.onSubTitleTap,
this.onTap,
required this.audioPageType,
Expand All @@ -40,12 +38,9 @@ class AudioTile extends StatefulWidget with WatchItStatefulWidgetMixin {
final bool selected;

final bool isPlayerPlaying;
final Future<void> Function() resume;
final void Function()? onTap;
final void Function() pause;
final void Function(String text)? onSubTitleTap;
final LibraryModel libraryModel;
final void Function(Audio audio) insertIntoQueue;
final void Function(Audio audio)? insertIntoQueue;
final bool showLeading;
final Color? selectedColor;

Expand All @@ -59,10 +54,16 @@ class _AudioTileState extends State<AudioTile> {
@override
Widget build(BuildContext context) {
final theme = context.t;
final playerModel = di<PlayerModel>();
final liked = watchPropertyValue((LibraryModel m) => m.liked(widget.audio));
final starred = watchPropertyValue(
(LibraryModel m) => m.isStarredStation(widget.audio.description),
);
final selectedColor = widget.selectedColor ?? theme.contrastyPrimary;
final subTitle = switch (widget.audioPageType) {
AudioPageType.artist => widget.audio.album ?? context.l10n.unknown,
AudioPageType.radioSearch =>
'${widget.audio.artist ?? context.l10n.unknown}, ${widget.audio.albumArtist ?? ''}, ${widget.audio.fileSize ?? ''} kbps',
_ => widget.audio.artist ?? context.l10n.unknown,
};

Expand All @@ -78,7 +79,7 @@ class _AudioTileState extends State<AudioTile> {
};

return MouseRegion(
key: ValueKey(widget.audio.audioType?.index),
key: ObjectKey(widget.audio),
onEnter: (e) {
if (isMobile) return;
setState(() => _hovered = true);
Expand All @@ -100,9 +101,9 @@ class _AudioTileState extends State<AudioTile> {
onTap: () {
if (widget.selected) {
if (widget.isPlayerPlaying) {
widget.pause();
playerModel.pause();
} else {
widget.resume();
playerModel.resume();
}
} else {
widget.onTap?.call();
Expand All @@ -124,11 +125,10 @@ class _AudioTileState extends State<AudioTile> {
),
trailing: _AudioTileTrail(
hovered: isMobile ? true : _hovered,
liked: liked,
liked: liked || starred,
audio: widget.audio,
selected: widget.selected,
isPlayerPlaying: widget.isPlayerPlaying,
libraryModel: widget.libraryModel,
pageId: widget.pageId,
audioPageType: widget.audioPageType,
insertIntoQueue: widget.insertIntoQueue,
Expand All @@ -144,10 +144,9 @@ class _AudioTileTrail extends StatelessWidget with WatchItMixin {
required this.audio,
required this.selected,
required this.isPlayerPlaying,
required this.libraryModel,
required this.pageId,
required this.audioPageType,
required this.insertIntoQueue,
this.insertIntoQueue,
required this.hovered,
required this.liked,
required this.selectedColor,
Expand All @@ -156,10 +155,9 @@ class _AudioTileTrail extends StatelessWidget with WatchItMixin {
final Audio audio;
final bool selected;
final bool isPlayerPlaying;
final LibraryModel libraryModel;
final String pageId;
final AudioPageType audioPageType;
final void Function(Audio audio) insertIntoQueue;
final void Function(Audio audio)? insertIntoQueue;
final bool hovered;
final bool liked;
final Color selectedColor;
Expand All @@ -173,11 +171,11 @@ class _AudioTileTrail extends StatelessWidget with WatchItMixin {
opacity: hovered || selected ? 1 : 0,
child: AudioTileOptionButton(
selected: selected && isPlayerPlaying,
libraryModel: libraryModel,
playlistId: pageId,
audio: audio,
allowRemove: audioPageType == AudioPageType.playlist,
insertIntoQueue: () => insertIntoQueue(audio),
insertIntoQueue:
insertIntoQueue != null ? () => insertIntoQueue!(audio) : null,
),
),
const SizedBox(
Expand Down
105 changes: 55 additions & 50 deletions lib/common/view/audio_tile_option_button.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';

import '../../extensions/build_context_x.dart';
Expand All @@ -17,7 +18,6 @@ class AudioTileOptionButton extends StatelessWidget {
required this.audio,
required this.playlistId,
required this.insertIntoQueue,
required this.libraryModel,
required this.allowRemove,
required this.selected,
});
Expand All @@ -26,60 +26,63 @@ class AudioTileOptionButton extends StatelessWidget {
final Audio audio;
final void Function()? insertIntoQueue;

final LibraryModel libraryModel;
final bool allowRemove;
final bool selected;

@override
Widget build(BuildContext context) {
final theme = context.t;
final libraryModel = di<LibraryModel>();

return PopupMenuButton(
tooltip: context.l10n.moreOptions,
padding: EdgeInsets.zero,
itemBuilder: (context) {
return [
PopupMenuItem(
onTap: () {
insertIntoQueue?.call();
showSnackBar(
context: context,
content: Text(
'${context.l10n.addedTo} ${context.l10n.queue}: ${audio.artist} - ${audio.title}',
),
);
},
child: YaruTile(
leading: Icon(Iconz().insertIntoQueue),
title: Text(context.l10n.playNext),
),
),
if (allowRemove)
if (audio.audioType != AudioType.radio)
PopupMenuItem(
onTap: () =>
libraryModel.removeAudioFromPlaylist(playlistId, audio),
child: YaruTile(
leading: Icon(Iconz().remove),
title: Text('${context.l10n.removeFrom} $playlistId'),
),
),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) {
return AddToPlaylistDialog(
audio: audio,
libraryModel: libraryModel,
onTap: () {
insertIntoQueue?.call();
showSnackBar(
context: context,
content: Text(
'${context.l10n.addedTo} ${context.l10n.queue}: ${audio.artist} - ${audio.title}',
),
);
},
child: YaruTile(
leading: Icon(Iconz().insertIntoQueue),
title: Text(context.l10n.playNext),
),
),
child: YaruTile(
leading: Icon(Iconz().plus),
title: Text(
'${context.l10n.addToPlaylist} ...',
if (audio.audioType != AudioType.radio)
if (allowRemove)
PopupMenuItem(
onTap: () =>
libraryModel.removeAudioFromPlaylist(playlistId, audio),
child: YaruTile(
leading: Icon(Iconz().remove),
title: Text('${context.l10n.removeFrom} $playlistId'),
),
),
if (audio.audioType != AudioType.radio)
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) {
return AddToPlaylistDialog(
audio: audio,
libraryModel: libraryModel,
);
},
),
child: YaruTile(
leading: Icon(Iconz().plus),
title: Text(
'${context.l10n.addToPlaylist} ...',
),
),
),
),
PopupMenuItem(
onTap: () => showDialog(
context: context,
Expand All @@ -94,22 +97,24 @@ class AudioTileOptionButton extends StatelessWidget {
),
),
),
PopupMenuItem(
enabled: false,
padding: EdgeInsets.zero,
child: Theme(
data: theme.copyWith(disabledColor: theme.colorScheme.onSurface),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 13),
child: StreamProviderRow(
iconColor: theme.colorScheme.onSurface,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
text: '${audio.artist ?? ''} - ${audio.title ?? ''}',
if (audio.audioType != AudioType.radio)
PopupMenuItem(
enabled: false,
padding: EdgeInsets.zero,
child: Theme(
data:
theme.copyWith(disabledColor: theme.colorScheme.onSurface),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 13),
child: StreamProviderRow(
iconColor: theme.colorScheme.onSurface,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
text: '${audio.artist ?? ''} - ${audio.title ?? ''}',
),
),
),
),
),
];
},
icon: Icon(Iconz().viewMore),
Expand Down
8 changes: 4 additions & 4 deletions lib/common/view/like_icon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ class RadioLikeIcon extends StatelessWidget with WatchItMixin {

watchPropertyValue((LibraryModel m) => m.starredStations.length);

final isStarredStation = libraryModel.isStarredStation(audio?.url);
final isStarredStation = libraryModel.isStarredStation(audio?.description);

final void Function()? onLike;
if (audio == null && audio?.url == null) {
if (audio == null && audio?.description == null) {
onLike = null;
} else {
onLike = () {
isStarredStation
? libraryModel.unStarStation(audio!.url!)
? libraryModel.unStarStation(audio!.description!)
: libraryModel.addStarredStation(
audio!.url!,
audio!.description!,
[audio!],
);
};
Expand Down
Loading

0 comments on commit aa3ed98

Please sign in to comment.