diff --git a/lib/localization/app_ar.arb b/lib/localization/app_ar.arb index 1928a51f..b77ef314 100644 --- a/lib/localization/app_ar.arb +++ b/lib/localization/app_ar.arb @@ -70,6 +70,7 @@ "offlineSongs": "Offline songs", "originalRecommendations": "Original algorithm for recommendations", "others": "آخرون", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "قائمة تشغيل", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_de.arb b/lib/localization/app_de.arb index 757b3a04..807d4547 100644 --- a/lib/localization/app_de.arb +++ b/lib/localization/app_de.arb @@ -70,6 +70,7 @@ "offlineSongs": "Offline-Songs", "originalRecommendations": "Originaler Algorithmus für Vorschläge", "others": "Andere", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_el.arb b/lib/localization/app_el.arb index 8832db88..9b7f80ec 100644 --- a/lib/localization/app_el.arb +++ b/lib/localization/app_el.arb @@ -70,6 +70,7 @@ "offlineSongs": "Τραγούδια εκτός σύνδεσης", "originalRecommendations": "Πρωτότυπος αλγόριθμος για συστάσεις", "others": "Άλλα", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Λίστα αναπαραγωγής", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_en.arb b/lib/localization/app_en.arb index 59f7cba8..13e5209e 100644 --- a/lib/localization/app_en.arb +++ b/lib/localization/app_en.arb @@ -70,6 +70,7 @@ "offlineSongs": "Offline songs", "originalRecommendations": "Original recommendation algorithm", "others": "Others", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_es.arb b/lib/localization/app_es.arb index 1d706c38..cda38886 100644 --- a/lib/localization/app_es.arb +++ b/lib/localization/app_es.arb @@ -70,6 +70,7 @@ "offlineSongs": "Canciones sin conexión", "originalRecommendations": "Algoritmo de recomendaciones original", "others": "Otros", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Lista de reproducción", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_fr.arb b/lib/localization/app_fr.arb index 84ca60f9..d868a173 100644 --- a/lib/localization/app_fr.arb +++ b/lib/localization/app_fr.arb @@ -70,6 +70,7 @@ "offlineSongs": "Titres hors ligne", "originalRecommendations": "Algorithme original pour les recommandations", "others": "Autres", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_hi.arb b/lib/localization/app_hi.arb index 8674f701..815f2397 100644 --- a/lib/localization/app_hi.arb +++ b/lib/localization/app_hi.arb @@ -70,6 +70,7 @@ "offlineSongs": "ऑफ़लाइन गाने", "originalRecommendations": "मूल अनुशंसा एल्गोरिथ्म", "others": "अन्य", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "प्लेलिस्ट", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_id.arb b/lib/localization/app_id.arb index 248c5cc2..e95aec54 100644 --- a/lib/localization/app_id.arb +++ b/lib/localization/app_id.arb @@ -70,6 +70,7 @@ "offlineSongs": "Lagu offline", "originalRecommendations": "Algoritma rekomendasi asli", "others": "Lainnya", + "pickImageFromDevice": "Pick image from device", "playNext": "Putar berikutnya", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist sudah diunduh", diff --git a/lib/localization/app_it.arb b/lib/localization/app_it.arb index 77c8fe63..22a49ab0 100644 --- a/lib/localization/app_it.arb +++ b/lib/localization/app_it.arb @@ -70,6 +70,7 @@ "offlineSongs": "Brani offline", "originalRecommendations": "Algoritmo originale per le raccomandazioni", "others": "Altri", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_ja.arb b/lib/localization/app_ja.arb index 6ad59f5f..e7d7f8be 100644 --- a/lib/localization/app_ja.arb +++ b/lib/localization/app_ja.arb @@ -70,6 +70,7 @@ "offlineSongs": "オフライン用の曲", "originalRecommendations": "おすすめに独自アルゴリズムを使用", "others": "ほか", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "再生リスト", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_ko.arb b/lib/localization/app_ko.arb index ab67028b..35968673 100644 --- a/lib/localization/app_ko.arb +++ b/lib/localization/app_ko.arb @@ -70,6 +70,7 @@ "offlineSongs": "오프라인 노래", "originalRecommendations": "추천을 위한 오리지널 알고리즘", "others": "기타", + "pickImageFromDevice": "Pick image from device", "playNext": "다음 재생", "playlist": "재생목록", "playlistAlreadyDownloaded": "재생목록이 이미 다운로드되었음", @@ -124,4 +125,4 @@ "undo": "실행 취소", "userPlaylists": "사용자 재생목록", "youtubePlaylistLinkOrId": "유튜브 재생목록 링크 또는 ID " -} +} \ No newline at end of file diff --git a/lib/localization/app_pl.arb b/lib/localization/app_pl.arb index 050233aa..7671be27 100644 --- a/lib/localization/app_pl.arb +++ b/lib/localization/app_pl.arb @@ -70,6 +70,7 @@ "offlineSongs": "Utwory offline", "originalRecommendations": "Oryginalny algorytm rekomendacji", "others": "Inne", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlista", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_pt.arb b/lib/localization/app_pt.arb index 7eabacdd..dd937af6 100644 --- a/lib/localization/app_pt.arb +++ b/lib/localization/app_pt.arb @@ -70,6 +70,7 @@ "offlineSongs": "Músicas offline", "originalRecommendations": "Recomendações originais", "others": "Outros", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Playlist", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_ru.arb b/lib/localization/app_ru.arb index dcb042d0..181e1d2b 100644 --- a/lib/localization/app_ru.arb +++ b/lib/localization/app_ru.arb @@ -70,6 +70,7 @@ "offlineSongs": "Треки без интернета", "originalRecommendations": "Оригинальный алгоритм рекомендаций", "others": "Ещё", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Плейлист", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_tr.arb b/lib/localization/app_tr.arb index 958bcb5e..ea905a63 100644 --- a/lib/localization/app_tr.arb +++ b/lib/localization/app_tr.arb @@ -70,6 +70,7 @@ "offlineSongs": "Çevrimdışı Parçalar", "originalRecommendations": "Asıl Öneri Algoritması", "others": "Diğerleri", + "pickImageFromDevice": "Pick image from device", "playNext": "Bundan Sonra Oynat", "playlist": "Çalma Listeleri", "playlistAlreadyDownloaded": "Çalma listesi hali hazırda indirildi", @@ -124,4 +125,4 @@ "undo": "Geri Al", "userPlaylists": "Kullanıcı Listeleri", "youtubePlaylistLinkOrId": "YouTube listesi bağlantısı veya ID'si" -} +} \ No newline at end of file diff --git a/lib/localization/app_uk.arb b/lib/localization/app_uk.arb index d666f46f..0ee252fc 100644 --- a/lib/localization/app_uk.arb +++ b/lib/localization/app_uk.arb @@ -70,6 +70,7 @@ "offlineSongs": "Offline songs", "originalRecommendations": "Original algorithm for recommendations", "others": "Інше", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "Плейлист", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_zh-Hant.arb b/lib/localization/app_zh-Hant.arb index 469a5c32..bf76ac2d 100644 --- a/lib/localization/app_zh-Hant.arb +++ b/lib/localization/app_zh-Hant.arb @@ -70,6 +70,7 @@ "offlineSongs": "已離線歌曲", "originalRecommendations": "原始推薦算法", "others": "其他", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "播放列表", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/localization/app_zh.arb b/lib/localization/app_zh.arb index c96d7efe..9c10cce1 100644 --- a/lib/localization/app_zh.arb +++ b/lib/localization/app_zh.arb @@ -70,6 +70,7 @@ "offlineSongs": "已离线歌曲", "originalRecommendations": "原始推荐算法", "others": "其他", + "pickImageFromDevice": "Pick image from device", "playNext": "Play next", "playlist": "播放列表", "playlistAlreadyDownloaded": "Playlist already downloaded", diff --git a/lib/screens/library_page.dart b/lib/screens/library_page.dart index 84b2e6f1..90685bae 100644 --- a/lib/screens/library_page.dart +++ b/lib/screens/library_page.dart @@ -19,6 +19,9 @@ * please visit: https://github.com/gokadzev/Musify */ +import 'dart:convert'; + +import 'package:file_picker/file_picker.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:musify/API/musify.dart'; @@ -232,6 +235,7 @@ class _LibraryPageState extends State { var customPlaylistName = ''; var isYouTubeMode = true; String? imageUrl; + String? imageBase64; return StatefulBuilder( builder: (context, setState) { @@ -240,6 +244,76 @@ class _LibraryPageState extends State { final inactiveButtonBackground = theme.colorScheme.secondaryContainer; final dialogBackgroundColor = theme.dialogTheme.backgroundColor; + Future _pickImage() async { + final result = await FilePicker.platform.pickFiles( + type: FileType.image, + withData: true, + ); + if (result != null && result.files.single.bytes != null) { + final file = result.files.single; + String? mimeType; + if (file.extension != null) { + switch (file.extension!.toLowerCase()) { + case 'jpg': + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'png': + mimeType = 'image/png'; + break; + case 'gif': + mimeType = 'image/gif'; + break; + case 'bmp': + mimeType = 'image/bmp'; + break; + case 'webp': + mimeType = 'image/webp'; + break; + default: + mimeType = 'application/octet-stream'; + } + } else { + mimeType = 'application/octet-stream'; + } + setState(() { + imageBase64 = + 'data:$mimeType;base64,${base64Encode(file.bytes!)}'; + imageUrl = null; + }); + } + } + + Widget _imagePreview() { + if (imageBase64 != null) { + final base64Data = + imageBase64!.contains(',') + ? imageBase64!.split(',').last + : imageBase64!; + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Image.memory( + base64Decode(base64Data), + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ); + } else if (imageUrl != null && imageUrl!.isNotEmpty) { + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Image.network( + imageUrl!, + width: 80, + height: 80, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => const Icon(Icons.broken_image), + ), + ); + } + return const SizedBox.shrink(); + } + return AlertDialog( backgroundColor: dialogBackgroundColor, content: SingleChildScrollView( @@ -256,6 +330,7 @@ class _LibraryPageState extends State { id = ''; customPlaylistName = ''; imageUrl = null; + imageBase64 = null; }); }, style: ElevatedButton.styleFrom( @@ -274,6 +349,7 @@ class _LibraryPageState extends State { id = ''; customPlaylistName = ''; imageUrl = null; + imageBase64 = null; }); }, style: ElevatedButton.styleFrom( @@ -305,15 +381,40 @@ class _LibraryPageState extends State { customPlaylistName = value; }, ), - const SizedBox(height: 7), - TextField( - decoration: InputDecoration( - labelText: context.l10n!.customPlaylistImgUrl, + if (imageBase64 == null) ...[ + const SizedBox(height: 7), + TextField( + decoration: InputDecoration( + labelText: context.l10n!.customPlaylistImgUrl, + ), + onChanged: (value) { + imageUrl = value; + imageBase64 = null; + setState(() {}); + }, ), - onChanged: (value) { - imageUrl = value; - }, - ), + ], + const SizedBox(height: 7), + if (imageUrl == null) ...[ + Row( + children: [ + ElevatedButton.icon( + onPressed: _pickImage, + icon: const Icon(Icons.image), + label: Text(context.l10n!.pickImageFromDevice), + ), + if (imageBase64 != null) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon( + Icons.check_circle, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + _imagePreview(), + ], ], ], ), @@ -329,7 +430,7 @@ class _LibraryPageState extends State { context, createCustomPlaylist( customPlaylistName, - imageUrl, + imageBase64 ?? imageUrl, context, ), ); diff --git a/lib/screens/playlist_page.dart b/lib/screens/playlist_page.dart index 446f03b9..ce5c3467 100644 --- a/lib/screens/playlist_page.dart +++ b/lib/screens/playlist_page.dart @@ -19,8 +19,10 @@ * please visit: https://github.com/gokadzev/Musify */ +import 'dart:convert'; import 'dart:math'; +import 'package:file_picker/file_picker.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -291,73 +293,184 @@ class _PlaylistPageState extends State { () => showDialog( context: context, builder: (BuildContext context) { - var customPlaylistName = _playlist['title']; - var imageUrl = _playlist['image']; + String customPlaylistName = _playlist['title']; + String? imageUrl = _playlist['image']; + var imageBase64 = + (imageUrl != null && imageUrl.startsWith('data:')) + ? imageUrl + : null; + if (imageBase64 != null) imageUrl = null; - return AlertDialog( - content: SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 7), - TextField( - controller: TextEditingController( - text: customPlaylistName, + return StatefulBuilder( + builder: (context, setState) { + Future _pickImage() async { + final result = await FilePicker.platform.pickFiles( + type: FileType.image, + withData: true, + ); + if (result != null && result.files.single.bytes != null) { + final file = result.files.single; + String? mimeType; + if (file.extension != null) { + switch (file.extension!.toLowerCase()) { + case 'jpg': + case 'jpeg': + mimeType = 'image/jpeg'; + break; + case 'png': + mimeType = 'image/png'; + break; + case 'gif': + mimeType = 'image/gif'; + break; + case 'bmp': + mimeType = 'image/bmp'; + break; + case 'webp': + mimeType = 'image/webp'; + break; + default: + mimeType = 'application/octet-stream'; + } + } else { + mimeType = 'application/octet-stream'; + } + setState(() { + imageBase64 = + 'data:$mimeType;base64,${base64Encode(file.bytes!)}'; + imageUrl = null; + }); + } + } + + Widget _imagePreview() { + if (imageBase64 != null) { + final base64Data = + imageBase64!.contains(',') + ? imageBase64!.split(',').last + : imageBase64!; + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Image.memory( + base64Decode(base64Data), + width: 80, + height: 80, + fit: BoxFit.cover, ), - decoration: InputDecoration( - labelText: context.l10n!.customPlaylistName, + ); + } else if (imageUrl != null && imageUrl!.isNotEmpty) { + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Image.network( + imageUrl!, + width: 80, + height: 80, + fit: BoxFit.cover, + errorBuilder: + (_, __, ___) => const Icon(Icons.broken_image), ), - onChanged: (value) { - customPlaylistName = value; - }, + ); + } + return const SizedBox.shrink(); + } + + return AlertDialog( + content: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 7), + TextField( + controller: TextEditingController( + text: customPlaylistName, + ), + decoration: InputDecoration( + labelText: context.l10n!.customPlaylistName, + ), + onChanged: (value) { + customPlaylistName = value; + }, + ), + if (imageBase64 == null) ...[ + const SizedBox(height: 7), + TextField( + controller: TextEditingController(text: imageUrl), + decoration: InputDecoration( + labelText: context.l10n!.customPlaylistImgUrl, + ), + onChanged: (value) { + imageUrl = value; + imageBase64 = null; + setState(() {}); + }, + ), + ], + const SizedBox(height: 7), + if (imageUrl == null) ...[ + Row( + children: [ + ElevatedButton.icon( + onPressed: _pickImage, + icon: const Icon(Icons.image), + label: Text( + context.l10n!.pickImageFromDevice, + ), + ), + if (imageBase64 != null) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon( + Icons.check_circle, + color: + Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + _imagePreview(), + ], + ], ), - const SizedBox(height: 7), - TextField( - controller: TextEditingController(text: imageUrl), - decoration: InputDecoration( - labelText: context.l10n!.customPlaylistImgUrl, - ), - onChanged: (value) { - imageUrl = value; + ), + actions: [ + TextButton( + child: Text(context.l10n!.add.toUpperCase()), + onPressed: () { + setState(() { + final index = userCustomPlaylists.value.indexOf( + widget.playlistData, + ); + + if (index != -1) { + final newPlaylist = { + 'title': customPlaylistName, + 'source': 'user-created', + if (imageBase64 != null) + 'image': imageBase64 + else if (imageUrl != null) + 'image': imageUrl, + 'list': widget.playlistData['list'], + }; + final updatedPlaylists = List.from( + userCustomPlaylists.value, + ); + updatedPlaylists[index] = newPlaylist; + userCustomPlaylists.value = updatedPlaylists; + addOrUpdateData( + 'user', + 'customPlaylists', + userCustomPlaylists.value, + ); + _playlist = newPlaylist; + showToast(context, context.l10n!.playlistUpdated); + } + + Navigator.pop(context); + }); }, ), ], - ), - ), - actions: [ - TextButton( - child: Text(context.l10n!.add.toUpperCase()), - onPressed: () { - setState(() { - final index = userCustomPlaylists.value.indexOf( - widget.playlistData, - ); - - if (index != -1) { - final newPlaylist = { - 'title': customPlaylistName, - 'source': 'user-created', - if (imageUrl != null) 'image': imageUrl, - 'list': widget.playlistData['list'], - }; - final updatedPlaylists = List.from( - userCustomPlaylists.value, - ); - updatedPlaylists[index] = newPlaylist; - userCustomPlaylists.value = updatedPlaylists; - addOrUpdateData( - 'user', - 'customPlaylists', - userCustomPlaylists, - ); - _playlist = newPlaylist; - showToast(context, context.l10n!.playlistUpdated); - } - - Navigator.pop(context); - }); - }, - ), - ], + ); + }, ); }, ), diff --git a/lib/widgets/playlist_artwork.dart b/lib/widgets/playlist_artwork.dart new file mode 100644 index 00000000..66582b6b --- /dev/null +++ b/lib/widgets/playlist_artwork.dart @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2025 Valeri Gokadze + * + * Musify is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Musify is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * For more information about Musify, including how to contribute, + * please visit: https://github.com/gokadzev/Musify + */ + +import 'dart:convert'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/material.dart'; +import 'package:musify/utilities/common_variables.dart'; +import 'package:musify/widgets/no_artwork_cube.dart'; + +class PlaylistArtwork extends StatelessWidget { + const PlaylistArtwork({ + super.key, + required this.playlistArtwork, + this.playlistTitle, + this.cubeIcon = FluentIcons.music_note_1_24_regular, + this.iconSize = 30, + this.size = 220, + }); + + final String? playlistArtwork; + final String? playlistTitle; + final IconData cubeIcon; + final double iconSize; + final double size; + + Widget _nullArtwork() => NullArtworkWidget( + icon: cubeIcon, + iconSize: iconSize, + size: size, + title: playlistTitle, + ); + + @override + Widget build(BuildContext context) { + final image = playlistArtwork; + if (image == null) return _nullArtwork(); + + if (image.startsWith('data:image')) { + final commaIdx = image.indexOf(','); + if (commaIdx == -1) return _nullArtwork(); + try { + final bytes = base64Decode(image.substring(commaIdx + 1)); + return SizedBox( + width: size, + height: size, + child: ClipRRect( + borderRadius: commonBarRadius, + child: Image.memory( + bytes, + height: size, + width: size, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => _nullArtwork(), + ), + ), + ); + } catch (_) { + return _nullArtwork(); + } + } + + if (image.startsWith('http')) { + return CachedNetworkImage( + key: Key(image), + height: size, + width: size, + imageUrl: image, + fit: BoxFit.cover, + imageBuilder: + (_, imageProvider) => SizedBox( + width: size, + height: size, + child: ClipRRect( + borderRadius: commonBarRadius, + child: Image(image: imageProvider), + ), + ), + errorWidget: (_, __, ___) => _nullArtwork(), + ); + } + + return _nullArtwork(); + } +} diff --git a/lib/widgets/playlist_bar.dart b/lib/widgets/playlist_bar.dart index f095a64c..b5f5cf3d 100644 --- a/lib/widgets/playlist_bar.dart +++ b/lib/widgets/playlist_bar.dart @@ -19,14 +19,13 @@ * please visit: https://github.com/gokadzev/Musify */ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:musify/API/musify.dart'; import 'package:musify/extensions/l10n.dart'; import 'package:musify/screens/playlist_page.dart'; import 'package:musify/utilities/common_variables.dart'; -import 'package:musify/widgets/no_artwork_cube.dart'; +import 'package:musify/widgets/playlist_artwork.dart'; class PlaylistBar extends StatelessWidget { PlaylistBar( @@ -100,7 +99,12 @@ class PlaylistBar extends StatelessWidget { padding: commonBarContentPadding, child: Row( children: [ - _buildAlbumArt(), + PlaylistArtwork( + playlistArtwork: playlistArtwork, + size: artworkSize, + iconSize: iconSize, + cubeIcon: cubeIcon, + ), const SizedBox(width: 8), Expanded( child: Column( @@ -126,37 +130,6 @@ class PlaylistBar extends StatelessWidget { ); } - Widget _buildAlbumArt() { - return playlistArtwork != null - ? CachedNetworkImage( - key: Key(playlistArtwork.toString()), - height: artworkSize, - width: artworkSize, - imageUrl: playlistArtwork.toString(), - fit: BoxFit.cover, - imageBuilder: - (context, imageProvider) => SizedBox( - width: artworkSize, - height: artworkSize, - child: ClipRRect( - borderRadius: commonBarRadius, - child: Image(image: imageProvider), - ), - ), - errorWidget: - (context, url, error) => NullArtworkWidget( - icon: cubeIcon, - iconSize: iconSize, - size: artworkSize, - ), - ) - : NullArtworkWidget( - icon: cubeIcon, - iconSize: iconSize, - size: artworkSize, - ); - } - Widget _buildActionButtons(BuildContext context, Color primaryColor) { return PopupMenuButton( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), diff --git a/lib/widgets/playlist_cube.dart b/lib/widgets/playlist_cube.dart index e66d881c..90726e80 100644 --- a/lib/widgets/playlist_cube.dart +++ b/lib/widgets/playlist_cube.dart @@ -19,12 +19,11 @@ * please visit: https://github.com/gokadzev/Musify */ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:musify/API/musify.dart'; import 'package:musify/extensions/l10n.dart'; -import 'package:musify/widgets/no_artwork_cube.dart'; +import 'package:musify/widgets/playlist_artwork.dart'; class PlaylistCube extends StatelessWidget { PlaylistCube( @@ -46,7 +45,6 @@ class PlaylistCube extends StatelessWidget { static const double paddingValue = 4; static const double typeLabelOffset = 10; - static const double iconSize = 30; final ValueNotifier playlistLikeStatus; @@ -63,7 +61,11 @@ class PlaylistCube extends StatelessWidget { clipBehavior: Clip.antiAlias, child: Stack( children: [ - _buildImage(context), + PlaylistArtwork( + playlistArtwork: playlist['image'], + size: size, + cubeIcon: cubeIcon, + ), if (borderRadius == 13 && playlist['image'] != null) Positioned( top: typeLabelOffset, @@ -75,30 +77,6 @@ class PlaylistCube extends StatelessWidget { ); } - Widget _buildImage(BuildContext context) { - return playlist['image'] != null - ? CachedNetworkImage( - key: ValueKey(playlist['image'].toString()), - imageUrl: playlist['image'].toString(), - height: size, - width: size, - fit: BoxFit.cover, - errorWidget: - (context, url, error) => NullArtworkWidget( - icon: cubeIcon, - iconSize: iconSize, - size: size, - title: playlist['title'], - ), - ) - : NullArtworkWidget( - icon: cubeIcon, - iconSize: iconSize, - size: size, - title: playlist['title'], - ); - } - Widget _buildLabel(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container(