Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/models/radio_station.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class RadioStation {
/// See [canEdit] for sourcing.
bool canDelete;

/// Whether the current user has favorited this radio station.
bool favorite;

RadioStation({
required this.id,
required this.name,
Expand All @@ -27,6 +30,7 @@ class RadioStation {
this.isPublic = false,
this.canEdit = false,
this.canDelete = false,
this.favorite = false,
});

factory RadioStation.fromJson(Map<String, dynamic> json) {
Expand All @@ -41,6 +45,7 @@ class RadioStation {
isPublic: json['is_public'] ?? false,
canEdit: permissions is Map ? permissions['edit'] == true : false,
canDelete: permissions is Map ? permissions['delete'] == true : false,
favorite: json['favorite'] == true,
);
}

Expand All @@ -50,6 +55,7 @@ class RadioStation {
String? url,
bool canEdit = false,
bool canDelete = false,
bool favorite = false,
}) {
final faker = Faker();
return RadioStation(
Expand All @@ -58,6 +64,7 @@ class RadioStation {
url: url ?? 'https://stream.example.com/live',
canEdit: canEdit,
canDelete: canDelete,
favorite: favorite,
);
}
}
14 changes: 14 additions & 0 deletions lib/providers/radio_player_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,20 @@ class RadioPlayerProvider with ChangeNotifier {
}
}

/// Re-emit the OS media-session metadata for the current station.
/// Use after editing the station's name or logo so the lock-screen /
/// notification controls pick up the new values without restarting
/// the stream.
void refreshMediaItem() {
final station = _currentStation;
if (station == null) return;

audioHandler.mediaItem.add(mediaItemForStation(
station,
streamTitle: _streamTitle,
));
}

void _startNowPlayingPolling(RadioStation station) {
_stopNowPlayingPolling();
_fetchNowPlaying(station);
Expand Down
17 changes: 17 additions & 0 deletions lib/providers/radio_station_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ class RadioStationProvider with ChangeNotifier, StreamSubscriber {
notifyListeners();
}

Future<void> toggleFavorite(RadioStation station) async {
// Optimistic flip + restore on failure.
station.favorite = !station.favorite;
notifyListeners();

try {
await post('favorites/toggle', data: {
'type': 'radio-station',
'id': station.id,
});
} catch (_) {
station.favorite = !station.favorite;
notifyListeners();
rethrow;
}
}

Future<Map<String, dynamic>> getNowPlaying(RadioStation station) async {
return await get('radio/stations/${station.id}/now-playing');
}
Expand Down
20 changes: 20 additions & 0 deletions lib/ui/screens/edit_radio_station_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Future<void> showEditRadioStationDialog(
TextEditingController(text: station.description ?? '');
var isPublic = station.isPublic;
final stationProvider = context.read<RadioStationProvider>();
final radioPlayer = context.read<RadioPlayerProvider>();

await showFormSheet(
context,
Expand All @@ -27,6 +28,11 @@ Future<void> showEditRadioStationDialog(
final url = urlController.text.trim();
if (name.isEmpty || url.isEmpty) return;

// Capture before mutation so we can detect what actually changed
// and bring the live player into sync.
final oldUrl = station.url;
final oldName = station.name;

try {
await stationProvider.update(
station,
Expand All @@ -35,6 +41,20 @@ Future<void> showEditRadioStationDialog(
description: descController.text.trim(),
isPublic: isPublic,
);

// If we just edited the station that's currently on air, the
// player is still streaming the old URL (its setUrl was called
// at play time). Restart the stream when the URL changed;
// otherwise just refresh the OS media-session metadata so the
// lock screen / notification picks up the new name.
if (radioPlayer.currentStation?.id == station.id) {
if (url != oldUrl) {
radioPlayer.play(station).catchError((_) {});
} else if (name != oldName) {
radioPlayer.refreshMediaItem();
}
}

Navigator.pop(context);
showOverlay(context, caption: 'Station updated');
} catch (_) {
Expand Down
16 changes: 14 additions & 2 deletions lib/ui/screens/playable_action_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ class PlayableActionButton extends StatelessWidget {
final Function onTap;
final bool hideSheetOnTap;
final bool enabled;
final bool destructive;

const PlayableActionButton({
Key? key,
Expand All @@ -333,16 +334,27 @@ class PlayableActionButton extends StatelessWidget {
required this.onTap,
this.hideSheetOnTap = true,
this.enabled = true,
this.destructive = false,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final destructiveColor = CupertinoColors.systemRed.resolveFrom(context);
final Color? labelColor = !enabled
? Colors.white30
: destructive
? destructiveColor
: null;
final effectiveIcon = destructive && enabled
? Icon(icon.icon, color: destructiveColor)
: icon;

return ListTile(
leading: icon,
leading: effectiveIcon,
minLeadingWidth: 16,
title: Text(
text,
style: enabled ? null : const TextStyle(color: Colors.white30),
style: labelColor == null ? null : TextStyle(color: labelColor),
),
onTap: enabled
? () {
Expand Down
Loading
Loading