From 44861a9f5c80625e0ff9a04dc9752dc186a0fa73 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 9 Jul 2024 22:13:05 +0600 Subject: [PATCH] fix: unescape html escape values #1300 --- lib/components/playbutton_card.dart | 17 ++++--------- .../sections/header/flexible_header.dart | 10 ++++---- lib/extensions/string.dart | 2 +- .../playlist/playlist_create_dialog.dart | 3 ++- lib/modules/stats/common/playlist_item.dart | 4 ++-- lib/pages/playlist/playlist.dart | 21 +++++++++++++--- lib/provider/spotify/playlist/favorite.dart | 14 +++++++++++ lib/provider/spotify/playlist/playlist.dart | 24 +++++++++++++++---- 8 files changed, 65 insertions(+), 30 deletions(-) diff --git a/lib/components/playbutton_card.dart b/lib/components/playbutton_card.dart index ffd91cd2e..a0b96ab8f 100644 --- a/lib/components/playbutton_card.dart +++ b/lib/components/playbutton_card.dart @@ -8,18 +8,10 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/hover_builder.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/string.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart'; -final htmlTagRegexp = RegExp(r"<[^>]*>", caseSensitive: true); - -String? useDescription(String? description) { - return useMemoized(() { - if (description == null) return null; - return description.replaceAll(htmlTagRegexp, ''); - }, [description]); -} - class PlaybuttonCard extends HookWidget { final void Function()? onTap; final void Function()? onPlaybuttonPressed; @@ -66,8 +58,7 @@ class PlaybuttonCard extends HookWidget { others: 15, ); - final cleanDescription = useDescription(description); - + var unescapeHtml = description?.unescapeHtml(); return Container( constraints: BoxConstraints(maxWidth: size), margin: margin, @@ -205,11 +196,11 @@ class PlaybuttonCard extends HookWidget { overflow: TextOverflow.ellipsis, ), ), - if (cleanDescription != null) + if (description != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: AutoSizeText( - cleanDescription, + unescapeHtml!, maxLines: 2, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withOpacity(.5), diff --git a/lib/components/tracks_view/sections/header/flexible_header.dart b/lib/components/tracks_view/sections/header/flexible_header.dart index 6e8fc2d11..6845cc3e8 100644 --- a/lib/components/tracks_view/sections/header/flexible_header.dart +++ b/lib/components/tracks_view/sections/header/flexible_header.dart @@ -5,12 +5,12 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/playbutton_card.dart'; import 'package:spotube/components/tracks_view/sections/header/header_actions.dart'; import 'package:spotube/components/tracks_view/sections/header/header_buttons.dart'; import 'package:spotube/components/tracks_view/track_view_props.dart'; import 'package:gap/gap.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/string.dart'; import 'package:spotube/hooks/utils/use_palette_color.dart'; import 'package:spotube/utils/platform.dart'; @@ -24,8 +24,6 @@ class TrackViewFlexHeader extends HookConsumerWidget { final defaultTextStyle = DefaultTextStyle.of(context); final mediaQuery = MediaQuery.of(context); - final description = useDescription(props.description); - final palette = usePaletteColor(props.image, ref); return IconTheme( @@ -127,10 +125,10 @@ class TrackViewFlexHeader extends HookConsumerWidget { overflow: TextOverflow.ellipsis, ), const SizedBox(height: 10), - if (description != null && - description.isNotEmpty) + if (props.description != null && + props.description!.isNotEmpty) Text( - description, + props.description!.unescapeHtml(), style: defaultTextStyle.style.copyWith( color: palette.bodyTextColor, diff --git a/lib/extensions/string.dart b/lib/extensions/string.dart index 0aa41dc67..d3706f3f2 100644 --- a/lib/extensions/string.dart +++ b/lib/extensions/string.dart @@ -7,7 +7,7 @@ extension UnescapeHtml on String { } extension NullableUnescapeHtml on String? { - String? unescapeHtml() => this == null ? null : htmlEscape.convert(this!); + String? unescapeHtml() => this?.unescapeHtml(); } extension StringExtension on String { diff --git a/lib/modules/playlist/playlist_create_dialog.dart b/lib/modules/playlist/playlist_create_dialog.dart index 6c333986e..b9e4be8fc 100644 --- a/lib/modules/playlist/playlist_create_dialog.dart +++ b/lib/modules/playlist/playlist_create_dialog.dart @@ -15,6 +15,7 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/extensions/string.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; @@ -54,7 +55,7 @@ class PlaylistCreateDialog extends HookConsumerWidget { text: updatingPlaylist?.name, ); final description = useTextEditingController( - text: updatingPlaylist?.description, + text: updatingPlaylist?.description?.unescapeHtml(), ); final public = useState( updatingPlaylist?.public ?? false, diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index 79e40d717..515c97b37 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/playbutton_card.dart'; import 'package:spotube/extensions/image.dart'; +import 'package:spotube/extensions/string.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -28,7 +28,7 @@ class StatsPlaylistItem extends StatelessWidget { ), title: Text(playlist.name!), subtitle: Text( - playlist.description!.replaceAll(htmlTagRegexp, ''), + playlist.description?.unescapeHtml() ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index f7c5a431e..31426f20e 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart' hide Page; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; @@ -12,19 +13,33 @@ import 'package:spotube/provider/spotify/spotify.dart'; class PlaylistPage extends HookConsumerWidget { static const name = "playlist"; - final PlaylistSimple playlist; + final PlaylistSimple _playlist; const PlaylistPage({ super.key, - required this.playlist, - }); + required PlaylistSimple playlist, + }) : _playlist = playlist; @override Widget build(BuildContext context, ref) { + final playlist = ref + .watch( + favoritePlaylistsProvider.select( + (value) => value.whenData( + (value) => + value.items.firstWhereOrNull((s) => s.id == _playlist.id), + ), + ), + ) + .asData + ?.value ?? + _playlist; + final tracks = ref.watch(playlistTracksProvider(playlist.id!)); final tracksNotifier = ref.watch(playlistTracksProvider(playlist.id!).notifier); final isFavoritePlaylist = ref.watch(isFavoritePlaylistProvider(playlist.id!)); + final favoritePlaylistsNotifier = ref.watch(favoritePlaylistsProvider.notifier); diff --git a/lib/provider/spotify/playlist/favorite.dart b/lib/provider/spotify/playlist/favorite.dart index a0e051aa6..000001ade 100644 --- a/lib/provider/spotify/playlist/favorite.dart +++ b/lib/provider/spotify/playlist/favorite.dart @@ -51,6 +51,20 @@ class FavoritePlaylistsNotifier ); } + void updatePlaylist(PlaylistSimple playlist) { + if (state.value == null) return; + + if (state.value!.items.none((e) => e.id == playlist.id)) return; + + state = AsyncData( + state.value!.copyWith( + items: state.value!.items + .map((element) => element.id == playlist.id ? playlist : element) + .toList(), + ), + ); + } + Future addFavorite(PlaylistSimple playlist) async { await update((state) async { await spotify.playlists.followPlaylist(playlist.id!); diff --git a/lib/provider/spotify/playlist/playlist.dart b/lib/provider/spotify/playlist/playlist.dart index fd420cd93..0eec3a87d 100644 --- a/lib/provider/spotify/playlist/playlist.dart +++ b/lib/provider/spotify/playlist/playlist.dart @@ -71,16 +71,32 @@ class PlaylistNotifier extends FamilyAsyncNotifier { state.id!, input.base64Image!, ); + + final playlist = await spotify.playlists.get(state.id!); + + ref.read(favoritePlaylistsProvider.notifier).updatePlaylist(playlist); + return playlist; } - return spotify.playlists.get(state.id!); - } catch (e) { + final playlist = Playlist.fromJson( + { + ...state.toJson(), + 'name': input.playlistName, + 'collaborative': input.collaborative, + 'description': input.description, + 'public': input.public, + }, + ); + + ref.read(favoritePlaylistsProvider.notifier).updatePlaylist(playlist); + + return playlist; + } catch (e, stack) { onError?.call(e); + AppLogger.reportError(e, stack); rethrow; } }); - - ref.invalidate(favoritePlaylistsProvider); } }