diff --git a/lib/api/player.dart b/lib/api/player.dart index e20a105..e091d40 100644 --- a/lib/api/player.dart +++ b/lib/api/player.dart @@ -12,6 +12,7 @@ import 'package:freezer/ui/android_auto.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:just_audio/just_audio.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:just_audio_media_kit/just_audio_media_kit.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; @@ -432,6 +433,10 @@ class AudioPlayerTask extends BaseAudioHandler { } Future _init(AudioPlayerTaskInitArguments initArgs) async { + // Linux/Windows specific options + JustAudioMediaKit.title = 'Freezer'; + JustAudioMediaKit.protocolWhitelist = const ['http']; + _deezerAPI = initArgs.deezerAPI; _androidAuto = AndroidAuto(deezerAPI: _deezerAPI); _shouldLogTracks = initArgs.logListen; diff --git a/lib/page_routes/fade.dart b/lib/page_routes/fade.dart index 85a6a04..54a060d 100644 --- a/lib/page_routes/fade.dart +++ b/lib/page_routes/fade.dart @@ -7,6 +7,8 @@ class FadePageRoute extends BasicPageRoute { final bool barrierDismissible; @override final Color? barrierColor; + @override + final bool opaque; final WidgetBuilder builder; final bool blur; @@ -18,6 +20,7 @@ class FadePageRoute extends BasicPageRoute { super.settings, this.barrierColor, this.barrierDismissible = false, + this.opaque = true, }); @override diff --git a/lib/ui/cached_image.dart b/lib/ui/cached_image.dart index 9789f9e..bdf8f61 100644 --- a/lib/ui/cached_image.dart +++ b/lib/ui/cached_image.dart @@ -147,7 +147,8 @@ class ZoomableImage extends StatelessWidget { Navigator.of(context).push(FadePageRoute( builder: (context) => ZoomableImageRoute(imageUrl: url, heroKey: _key), - barrierDismissible: true)); + barrierDismissible: true, + opaque: false)); }, ); } @@ -163,7 +164,7 @@ class ZoomableImageRoute extends StatefulWidget { } class _ZoomableImageRouteState extends State { - bool photoViewOpened = false; + bool photoViewOpened = true; final controller = PhotoViewController(); final _focusNode = FocusScopeNode(); diff --git a/lib/ui/details_screens.dart b/lib/ui/details_screens.dart index ddadfd0..3c89961 100644 --- a/lib/ui/details_screens.dart +++ b/lib/ui/details_screens.dart @@ -73,51 +73,48 @@ class _AlbumDetailsState extends State { : ListView( children: [ //Album art, title, artists - Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 8.0), - ConstrainedBox( - constraints: BoxConstraints.loose( - MediaQuery.of(context).size / 3), - child: ZoomableImage( - url: album!.art!.full, - rounded: true, - ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8.0), + ConstrainedBox( + constraints: BoxConstraints.loose( + MediaQuery.of(context).size / 2.5), + child: ZoomableImage( + url: album!.art!.full, + rounded: true, ), - const SizedBox(height: 8.0), + ), + const SizedBox(height: 8.0), + Text( + album!.title!, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: const TextStyle( + fontSize: 20.0, fontWeight: FontWeight.bold), + ), + Text( + album!.artistString, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: TextStyle( + fontSize: 16.0, + color: Theme.of(context).primaryColor), + ), + const SizedBox(height: 4.0), + if (album!.releaseDate != null && + album!.releaseDate!.length >= 4) Text( - album!.title!, + album!.releaseDate!, textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, - style: const TextStyle( - fontSize: 20.0, fontWeight: FontWeight.bold), - ), - Text( - album!.artistString, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 2, style: TextStyle( - fontSize: 16.0, - color: Theme.of(context).primaryColor), + fontSize: 12.0, + color: Theme.of(context).disabledColor), ), - const SizedBox(height: 4.0), - if (album!.releaseDate != null && - album!.releaseDate!.length >= 4) - Text( - album!.releaseDate!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12.0, - color: Theme.of(context).disabledColor), - ), - const SizedBox(height: 8.0), - ], - ), + const SizedBox(height: 8.0), + ], ), const FreezerDivider(), //Details @@ -355,14 +352,15 @@ class _ArtistDetailsState extends State { const SizedBox(height: 4.0), Padding( padding: const EdgeInsets.all(16.0), - child: SizedBox( - height: MediaQuery.of(context).size.height / 3, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height / 3), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Flexible( child: ZoomableImage( - url: widget.artist.picture!.full, + url: artist.picture!.full, rounded: true, ), ), @@ -1090,12 +1088,8 @@ class _PlaylistDetailsState extends State { }, onSecondary: (details) { MenuSheet m = MenuSheet(context); m.defaultTrackMenu(t, details: details, options: [ - (playlist!.user!.id == deezerAPI.userId) - ? m.removeFromPlaylist(t, playlist) - : const SizedBox( - width: 0, - height: 0, - ) + if (playlist!.user!.id == deezerAPI.userId) + m.removeFromPlaylist(t, playlist) ]); }); }), @@ -1128,9 +1122,11 @@ class _MakePlaylistOfflineState extends State { @override void initState() { downloadManager.checkOffline(playlist: widget.playlist).then((v) { - setState(() { - _offline = v; - }); + if (mounted) { + setState(() { + _offline = v; + }); + } }); super.initState(); } @@ -1221,55 +1217,55 @@ class _ShowScreenState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - CachedImage( - url: _show!.art!.full, - rounded: true, - width: MediaQuery.of(context).size.width / 2 - 16, - ), - SizedBox( - width: MediaQuery.of(context).size.width / 2 - 16, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Text(_show!.name!, - maxLines: 2, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20.0, fontWeight: FontWeight.bold)), - Container(height: 8.0), - Text( - _show!.description!, - maxLines: 6, - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 16.0), - ) - ], + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height / 3), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: AspectRatio( + aspectRatio: 1.0, + child: CachedImage( + url: _show!.art!.full, + rounded: true, + ), + ), ), - ) - ], + SizedBox( + width: min(MediaQuery.of(context).size.width / 16, 60.0)), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_show!.name!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 20.0, fontWeight: FontWeight.bold)), + const SizedBox(height: 16.0), + Text( + _show!.description!, + maxLines: 6, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 16.0), + ) + ], + ), + ) + ], + ), ), ), - Container(height: 4.0), + const SizedBox(height: 4.0), const FreezerDivider(), //Error if (_error) const ErrorScreen(), //Loading - if (_loading) - const Padding( - padding: EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [CircularProgressIndicator()], - ), - ), + if (_loading) const Center(child: CircularProgressIndicator()), //Data if (!_loading && !_error) @@ -1277,16 +1273,10 @@ class _ShowScreenState extends State { ShowEpisode e = _episodes![i]; return ShowEpisodeTile( e, - trailing: IconButton( - icon: Icon( - Icons.more_vert, - semanticLabel: "Options".i18n, - ), - onPressed: () { - MenuSheet m = MenuSheet(context); - m.defaultShowEpisodeMenu(_show!, e); - }, - ), + onSecondary: (details) { + MenuSheet m = MenuSheet(context); + m.defaultShowEpisodeMenu(_show!, e, details: details); + }, onTap: () async { await playerHelper.playShowEpisode(_show!, _episodes!, index: i); diff --git a/lib/ui/home_screen.dart b/lib/ui/home_screen.dart index da3187a..4ad1170 100644 --- a/lib/ui/home_screen.dart +++ b/lib/ui/home_screen.dart @@ -340,9 +340,9 @@ class HomePageItemWidget extends StatelessWidget { Navigator.of(context) .pushRoute(builder: (context) => PlaylistDetails(item.value)); }, - onHold: () { + onSecondary: (details) { MenuSheet m = MenuSheet(context); - m.defaultPlaylistMenu(item.value); + m.defaultPlaylistMenu(item.value, details: details); }, ); case HomePageItemType.CHANNEL: diff --git a/lib/ui/library.dart b/lib/ui/library.dart index 5dcad32..89f21d0 100644 --- a/lib/ui/library.dart +++ b/lib/ui/library.dart @@ -1217,6 +1217,7 @@ class _HistoryScreenState extends State { MenuSheet m = MenuSheet(context); m.defaultTrackMenu(t, details: details); }, + checkTrackOffline: false, ); }, )), diff --git a/lib/ui/menu.dart b/lib/ui/menu.dart index b709978..5683d38 100644 --- a/lib/ui/menu.dart +++ b/lib/ui/menu.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:ffi'; import 'package:freezer/main.dart'; import 'package:freezer/ui/player_bar.dart'; @@ -93,17 +92,57 @@ class SliverTrackPersistentHeader extends SliverPersistentHeaderDelegate { } } +class MenuSheetOption { + final Widget label; + final Widget? icon; + final VoidCallback onTap; + + const MenuSheetOption( + this.label, { + required this.onTap, + this.icon, + }); +} + class MenuSheet { BuildContext context; Function? navigateCallback; MenuSheet(this.context, {this.navigateCallback}); + void _showContextMenu(List options, + {required TapDownDetails details}) { + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final actualPosition = overlay.globalToLocal(details.globalPosition); + showMenu( + elevation: 4.0, + context: context, + constraints: const BoxConstraints(maxWidth: 300), + position: + RelativeRect.fromSize(actualPosition & Size.zero, overlay.size), + items: options + .map((option) => PopupMenuItem( + onTap: option.onTap, + child: option.icon == null + ? option.label + : Row(mainAxisSize: MainAxisSize.min, children: [ + option.icon!, + const SizedBox(width: 8.0), + option.label, + ]))) + .toList(growable: false)); + } + //=================== // DEFAULT //=================== - void show(List options) { + void show(List options, {TapDownDetails? details}) { + if (details != null) { + _showContextMenu(options, details: details); + return; + } + showModalBottomSheet( isScrollControlled: false, // true, context: context, @@ -117,7 +156,14 @@ class MenuSheet { : 350, ), child: SingleChildScrollView( - child: Column(children: options), + child: Column( + children: options + .map((option) => ListTile( + title: option.label, + leading: option.icon, + onTap: option.onTap, + )) + .toList(growable: false)), ), ), ); @@ -128,7 +174,13 @@ class MenuSheet { // TRACK //=================== - void showWithTrack(Track track, List options) { + void showWithTrack(Track track, List options, + {TapDownDetails? details}) { + if (details != null) { + _showContextMenu(options, details: details); + return; + } + showModalBottomSheet( backgroundColor: Colors.transparent, context: context, @@ -154,7 +206,14 @@ class MenuSheet { pinned: true, delegate: SliverTrackPersistentHeader(track, extent: 128.0 + 16.0 + 16.0)), - SliverList(delegate: SliverChildListDelegate.fixed(options)), + SliverList( + delegate: SliverChildListDelegate.fixed(options + .map((option) => ListTile( + title: option.label, + leading: option.icon, + onTap: option.onTap, + )) + .toList(growable: false))), ], ), ), @@ -165,53 +224,53 @@ class MenuSheet { //Default track options void defaultTrackMenu( Track track, { - List options = const [], + List options = const [], Function? onRemove, TapDownDetails? details, }) { - showWithTrack(track, [ - addToQueueNext(track), - addToQueue(track), - (cache.checkTrackFavorite(track)) - ? removeFavoriteTrack(track, onUpdate: onRemove) - : addTrackFavorite(track), - addToPlaylist(track), - downloadTrack(track), - offlineTrack(track), - shareTile('track', track.id), - playMix(track), - showAlbum(track.album!), - ...List.generate( - track.artists!.length, (i) => showArtist(track.artists![i])), - ...options - ]); + showWithTrack( + track, + [ + addToQueueNext(track), + addToQueue(track), + (cache.checkTrackFavorite(track)) + ? removeFavoriteTrack(track, onUpdate: onRemove) + : addTrackFavorite(track), + addToPlaylist(track), + downloadTrack(track), + offlineTrack(track), + shareTile('track', track.id), + playMix(track), + showAlbum(track.album!), + ...List.generate( + track.artists!.length, (i) => showArtist(track.artists![i])), + ...options + ], + details: details); } //=================== // TRACK OPTIONS //=================== - Widget addToQueueNext(Track t) => ListTile( - title: Text('Play next'.i18n), - leading: const Icon(Icons.playlist_play), - onTap: () async { + MenuSheetOption addToQueueNext(Track t) => + MenuSheetOption(Text('Play next'.i18n), + icon: const Icon(Icons.playlist_play), onTap: () async { //-1 = next await audioHandler.insertQueueItem(-1, await t.toMediaItem()); _close(); }); - Widget addToQueue(Track t) => ListTile( - title: Text('Add to queue'.i18n), - leading: const Icon(Icons.playlist_add), - onTap: () async { + MenuSheetOption addToQueue(Track t) => + MenuSheetOption(Text('Add to queue'.i18n), + icon: const Icon(Icons.playlist_add), onTap: () async { await audioHandler.addQueueItem(await t.toMediaItem()); _close(); }); - Widget addTrackFavorite(Track t) => ListTile( - title: Text('Add track to favorites'.i18n), - leading: const Icon(Icons.favorite), - onTap: () async { + MenuSheetOption addTrackFavorite(Track t) => + MenuSheetOption(Text('Add track to favorites'.i18n), + icon: const Icon(Icons.favorite), onTap: () async { await deezerAPI.addFavoriteTrack(t.id); //Make track offline, if favorites are offline Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!); @@ -225,9 +284,9 @@ class MenuSheet { _close(); }); - Widget downloadTrack(Track t) => ListTile( - title: Text('Download'.i18n), - leading: const Icon(Icons.file_download), + MenuSheetOption downloadTrack(Track t) => MenuSheetOption( + Text('Download'.i18n), + icon: const Icon(Icons.file_download), onTap: () async { if (await downloadManager.addOfflineTrack(t, private: false, context: context, isSingleton: true) != @@ -236,9 +295,9 @@ class MenuSheet { }, ); - Widget addToPlaylist(Track t) => ListTile( - title: Text('Add to playlist'.i18n), - leading: const Icon(Icons.playlist_add), + MenuSheetOption addToPlaylist(Track t) => MenuSheetOption( + Text('Add to playlist'.i18n), + icon: const Icon(Icons.playlist_add), onTap: () async { //Show dialog to pick playlist await showDialog( @@ -260,9 +319,9 @@ class MenuSheet { }, ); - Widget removeFromPlaylist(Track t, Playlist? p) => ListTile( - title: Text('Remove from playlist'.i18n), - leading: const Icon(Icons.delete), + MenuSheetOption removeFromPlaylist(Track t, Playlist? p) => MenuSheetOption( + Text('Remove from playlist'.i18n), + icon: const Icon(Icons.delete), onTap: () async { await deezerAPI.removeFromPlaylist(t.id, p!.id); ScaffoldMessenger.of(context) @@ -271,9 +330,9 @@ class MenuSheet { }, ); - Widget removeFavoriteTrack(Track t, {onUpdate}) => ListTile( - title: Text('Remove favorite'.i18n), - leading: const Icon(Icons.delete), + MenuSheetOption removeFavoriteTrack(Track t, {onUpdate}) => MenuSheetOption( + Text('Remove favorite'.i18n), + icon: const Icon(Icons.delete), onTap: () async { await deezerAPI.removeFavorite(t.id); //Check if favorites playlist is offline, update it @@ -282,9 +341,7 @@ class MenuSheet { await downloadManager.addOfflinePlaylist(p); } //Remove from cache - if (cache.libraryTracks != null) { - cache.libraryTracks!.removeWhere((i) => i == t.id); - } + cache.libraryTracks.removeWhere((i) => i == t.id); ScaffoldMessenger.of(context) .snack('Track removed from library'.i18n); if (onUpdate != null) onUpdate(); @@ -293,13 +350,13 @@ class MenuSheet { ); //Redirect to artist page (ie from track) - Widget showArtist(Artist a) => ListTile( - title: Text( + MenuSheetOption showArtist(Artist a) => MenuSheetOption( + Text( '${'Go to'.i18n} ${a.name}', maxLines: 1, overflow: TextOverflow.ellipsis, ), - leading: const Icon(Icons.recent_actors), + icon: const Icon(Icons.recent_actors), onTap: () { _close(); navigatorKey.currentState! @@ -311,13 +368,13 @@ class MenuSheet { }, ); - Widget showAlbum(Album a) => ListTile( - title: Text( + MenuSheetOption showAlbum(Album a) => MenuSheetOption( + Text( '${'Go to'.i18n} ${a.title}', maxLines: 1, overflow: TextOverflow.ellipsis, ), - leading: const Icon(Icons.album), + icon: const Icon(Icons.album), onTap: () { _close(); navigatorKey.currentState! @@ -329,36 +386,33 @@ class MenuSheet { }, ); - Widget playMix(Track track) => ListTile( - title: Text('Play mix'.i18n), - leading: const Icon(Icons.online_prediction), + MenuSheetOption playMix(Track track) => MenuSheetOption( + Text('Play mix'.i18n), + icon: const Icon(Icons.online_prediction), onTap: () async { playerHelper.playMix(track.id, track.title!); _close(); }, ); - Widget offlineTrack(Track track) => FutureBuilder( - future: downloadManager.checkOffline(track: track), - builder: (context, snapshot) { - bool isOffline = snapshot.data ?? track.offline ?? false; - return ListTile( - title: Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n), - leading: const Icon(Icons.offline_pin), - onTap: () async { - if (isOffline) { - await downloadManager.removeOfflineTracks([track]); - ScaffoldMessenger.of(context) - .snack("Track removed from offline!".i18n); - } else { - await downloadManager.addOfflineTrack(track, - private: true, context: context); - } - _close(); - }, - ); - }, - ); + MenuSheetOption offlineTrack(Track track) => MenuSheetOption( + FutureBuilder( + future: downloadManager.checkOffline(track: track), + builder: (context, snapshot) { + bool isOffline = snapshot.data ?? track.offline ?? false; + return Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n); + }), + icon: const Icon(Icons.offline_pin), onTap: () async { + if (await downloadManager.checkOffline(track: track)) { + await downloadManager.removeOfflineTracks([track]); + ScaffoldMessenger.of(context) + .snack("Track removed from offline!".i18n); + } else { + await downloadManager.addOfflineTrack(track, + private: true, context: context); + } + _close(); + }); //=================== // ALBUM @@ -366,7 +420,7 @@ class MenuSheet { //Default album options void defaultAlbumMenu(Album album, - {List options = const [], + {List options = const [], Function? onRemove, TapDownDetails? details}) { show([ @@ -384,19 +438,18 @@ class MenuSheet { // ALBUM OPTIONS //=================== - Widget downloadAlbum(Album a) => ListTile( - title: Text('Download'.i18n), - leading: const Icon(Icons.file_download), - onTap: () async { + MenuSheetOption downloadAlbum(Album a) => + MenuSheetOption(Text('Download'.i18n), + icon: const Icon(Icons.file_download), onTap: () async { _close(); if (await downloadManager.addOfflineAlbum(a, private: false, context: context) != false) showDownloadStartedToast(); }); - Widget offlineAlbum(Album a) => ListTile( - title: Text('Make offline'.i18n), - leading: const Icon(Icons.offline_pin), + MenuSheetOption offlineAlbum(Album a) => MenuSheetOption( + Text('Make offline'.i18n), + icon: const Icon(Icons.offline_pin), onTap: () async { await deezerAPI.addFavoriteAlbum(a.id); await downloadManager.addOfflineAlbum(a, private: true); @@ -405,9 +458,9 @@ class MenuSheet { }, ); - Widget libraryAlbum(Album a) => ListTile( - title: Text('Add to library'.i18n), - leading: const Icon(Icons.library_music), + MenuSheetOption libraryAlbum(Album a) => MenuSheetOption( + Text('Add to library'.i18n), + icon: const Icon(Icons.library_music), onTap: () async { await deezerAPI.addFavoriteAlbum(a.id); ScaffoldMessenger.of(context).snack('Added to library'.i18n); @@ -416,9 +469,9 @@ class MenuSheet { ); //Remove album from favorites - Widget removeAlbum(Album a, {Function? onRemove}) => ListTile( - title: Text('Remove album'.i18n), - leading: const Icon(Icons.delete), + MenuSheetOption removeAlbum(Album a, {Function? onRemove}) => MenuSheetOption( + Text('Remove album'.i18n), + icon: const Icon(Icons.delete), onTap: () async { await deezerAPI.removeAlbum(a.id); await downloadManager.removeOfflineAlbum(a.id); @@ -433,10 +486,10 @@ class MenuSheet { //=================== void defaultArtistMenu(Artist artist, - {List options = const [], + {List options = const [], Function? onRemove, TapDownDetails? details}) { - show([ + show(details: details, [ artist.library! ? removeArtist(artist, onRemove: onRemove) : favoriteArtist(artist), @@ -449,9 +502,10 @@ class MenuSheet { // ARTIST OPTIONS //=================== - Widget removeArtist(Artist a, {Function? onRemove}) => ListTile( - title: Text('Remove from favorites'.i18n), - leading: const Icon(Icons.delete), + MenuSheetOption removeArtist(Artist a, {Function? onRemove}) => + MenuSheetOption( + Text('Remove from favorites'.i18n), + icon: const Icon(Icons.delete), onTap: () async { await deezerAPI.removeArtist(a.id); ScaffoldMessenger.of(context) @@ -461,9 +515,9 @@ class MenuSheet { }, ); - Widget favoriteArtist(Artist a) => ListTile( - title: Text('Add to favorites'.i18n), - leading: const Icon(Icons.favorite), + MenuSheetOption favoriteArtist(Artist a) => MenuSheetOption( + Text('Add to favorites'.i18n), + icon: const Icon(Icons.favorite), onTap: () async { await deezerAPI.addFavoriteArtist(a.id); ScaffoldMessenger.of(context).snack('Added to library'.i18n); @@ -476,11 +530,11 @@ class MenuSheet { //=================== void defaultPlaylistMenu(Playlist playlist, - {List options = const [], + {List options = const [], Function? onRemove, Function? onUpdate, TapDownDetails? details}) { - show([ + show(details: details, [ if (playlist.library != null) playlist.library! ? removePlaylistLibrary(playlist, onRemove: onRemove) @@ -498,9 +552,10 @@ class MenuSheet { // PLAYLIST OPTIONS //=================== - Widget removePlaylistLibrary(Playlist p, {Function? onRemove}) => ListTile( - title: Text('Remove from library'.i18n), - leading: const Icon(Icons.delete), + MenuSheetOption removePlaylistLibrary(Playlist p, {Function? onRemove}) => + MenuSheetOption( + Text('Remove from library'.i18n), + icon: const Icon(Icons.delete), onTap: () async { if (p.user!.id!.trim() == deezerAPI.userId) { //Delete playlist if own @@ -515,9 +570,9 @@ class MenuSheet { }, ); - Widget addPlaylistLibrary(Playlist p) => ListTile( - title: Text('Add playlist to library'.i18n), - leading: const Icon(Icons.favorite), + MenuSheetOption addPlaylistLibrary(Playlist p) => MenuSheetOption( + Text('Add playlist to library'.i18n), + icon: const Icon(Icons.favorite), onTap: () async { await deezerAPI.addPlaylist(p.id); ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n); @@ -525,9 +580,9 @@ class MenuSheet { }, ); - Widget addPlaylistOffline(Playlist p) => ListTile( - title: Text('Make playlist offline'.i18n), - leading: const Icon(Icons.offline_pin), + MenuSheetOption addPlaylistOffline(Playlist p) => MenuSheetOption( + Text('Make playlist offline'.i18n), + icon: const Icon(Icons.offline_pin), onTap: () async { //Add to library await deezerAPI.addPlaylist(p.id); @@ -537,9 +592,9 @@ class MenuSheet { }, ); - Widget downloadPlaylist(Playlist p) => ListTile( - title: Text('Download playlist'.i18n), - leading: const Icon(Icons.file_download), + MenuSheetOption downloadPlaylist(Playlist p) => MenuSheetOption( + Text('Download playlist'.i18n), + icon: const Icon(Icons.file_download), onTap: () async { _close(); if (await downloadManager.addOfflinePlaylist(p, @@ -548,9 +603,10 @@ class MenuSheet { }, ); - Widget editPlaylist(Playlist p, {Function? onUpdate}) => ListTile( - title: Text('Edit playlist'.i18n), - leading: const Icon(Icons.edit), + MenuSheetOption editPlaylist(Playlist p, {Function? onUpdate}) => + MenuSheetOption( + Text('Edit playlist'.i18n), + icon: const Icon(Icons.edit), onTap: () async { await showDialog( context: context, @@ -565,8 +621,8 @@ class MenuSheet { //=================== defaultShowEpisodeMenu(Show s, ShowEpisode e, - {List options = const []}) { - show([ + {List options = const [], TapDownDetails? details}) { + show(details: details, [ shareTile('episode', e.id), shareShow(s.id), downloadExternalEpisode(e), @@ -574,18 +630,18 @@ class MenuSheet { ]); } - Widget shareShow(String? id) => ListTile( - title: Text('Share show'.i18n), - leading: const Icon(Icons.share), + MenuSheetOption shareShow(String? id) => MenuSheetOption( + Text('Share show'.i18n), + icon: const Icon(Icons.share), onTap: () async { Share.share('https://deezer.com/show/$id'); }, ); //Open direct download link in browser - Widget downloadExternalEpisode(ShowEpisode e) => ListTile( - title: Text('Download externally'.i18n), - leading: const Icon(Icons.file_download), + MenuSheetOption downloadExternalEpisode(ShowEpisode e) => MenuSheetOption( + Text('Download externally'.i18n), + icon: const Icon(Icons.file_download), onTap: () async { launchUrl(Uri.parse(e.url!)); }, @@ -608,17 +664,17 @@ class MenuSheet { }); } - Widget shareTile(String type, String? id) => ListTile( - title: Text('Share'.i18n), - leading: const Icon(Icons.share), + MenuSheetOption shareTile(String type, String? id) => MenuSheetOption( + Text('Share'.i18n), + icon: const Icon(Icons.share), onTap: () async { Share.share('https://deezer.com/$type/$id'); }, ); - Widget sleepTimer() => ListTile( - title: Text('Sleep timer'.i18n), - leading: const Icon(Icons.access_time), + MenuSheetOption sleepTimer() => MenuSheetOption( + Text('Sleep timer'.i18n), + icon: const Icon(Icons.access_time), onTap: () async { showDialog( context: context, @@ -628,9 +684,9 @@ class MenuSheet { }, ); - Widget wakelock() => ListTile( - title: Text('Keep the screen on'.i18n), - leading: const Icon(Icons.screen_lock_portrait), + MenuSheetOption wakelock() => MenuSheetOption( + Text('Keep the screen on'.i18n), + icon: const Icon(Icons.screen_lock_portrait), onTap: () async { _close(); //Enable diff --git a/lib/ui/player_bar.dart b/lib/ui/player_bar.dart index 489e920..7fd570d 100644 --- a/lib/ui/player_bar.dart +++ b/lib/ui/player_bar.dart @@ -237,7 +237,15 @@ class PlayerBar extends StatelessWidget { if (snapshot.data == null) { return Material( child: ListTile( - leading: Image.asset('assets/cover_thumb.jpg'), + dense: true, + visualDensity: VisualDensity.standard, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 6.0), + leading: Image.asset( + 'assets/cover_thumb.jpg', + width: 48.0, + height: 48.0, + ), title: Text('Nothing is currently playing'.i18n), ), ); @@ -254,10 +262,12 @@ class PlayerBar extends StatelessWidget { : image; return Material( child: ListTile( + dense: true, tileColor: _backgroundColor, focusNode: focusNode, - contentPadding: - const EdgeInsets.symmetric(horizontal: 8.0), + visualDensity: VisualDensity.standard, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 0.0), onTap: onTap, leading: AnimatedSwitcher( key: const ValueKey('player_bar_art_switcher'), diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index ff29b1e..02a1960 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_import + import 'dart:ui'; import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; @@ -691,16 +693,14 @@ class _FavoriteButtonState extends State { icon: libraryIcon, iconSize: widget.size, onPressed: () async { - cache.libraryTracks ??= []; - if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) { //Remove from library - setState(() => cache.libraryTracks!.remove(mediaItem.id)); + setState(() => cache.libraryTracks.remove(mediaItem.id)); await deezerAPI.removeFavorite(mediaItem.id); await cache.save(); } else { //Add - setState(() => cache.libraryTracks!.add(mediaItem.id)); + setState(() => cache.libraryTracks.add(mediaItem.id)); await deezerAPI.addFavoriteTrack(mediaItem.id); await cache.save(); } @@ -840,6 +840,7 @@ class _BigAlbumArtState extends State { context, FadePageRoute( barrierDismissible: true, + opaque: false, builder: (context) { final mediaItem = audioHandler.mediaItem.value!; return ZoomableImageRoute( @@ -867,7 +868,7 @@ class _BigAlbumArtState extends State { onHorizontalDragDown: (_) => _userScroll = true, // delayed a bit, so to make sure that the page view updated. onHorizontalDragEnd: (_) => Future.delayed( - const Duration(milliseconds: 500), () => _userScroll = false), + const Duration(milliseconds: 100), () => _userScroll = false), child: StreamBuilder>( stream: audioHandler.queue, initialData: audioHandler.queue.valueOrNull, diff --git a/lib/ui/queue_screen.dart b/lib/ui/queue_screen.dart index 2cadd22..c5da696 100644 --- a/lib/ui/queue_screen.dart +++ b/lib/ui/queue_screen.dart @@ -168,8 +168,9 @@ class _QueueListWidgetState extends State { } }); }, - onSecondary: (_) => MenuSheet(context) - .defaultTrackMenu(Track.fromMediaItem(mediaItem)), + onSecondary: (details) => MenuSheet(context).defaultTrackMenu( + Track.fromMediaItem(mediaItem), + details: details), checkTrackOffline: false, ), ), diff --git a/lib/ui/search.dart b/lib/ui/search.dart index 0c9384e..57b2d44 100644 --- a/lib/ui/search.dart +++ b/lib/ui/search.dart @@ -550,11 +550,17 @@ class SearchResultsScreen extends StatelessWidget { ), ), const SizedBox(height: 4.0), - SingleChildScrollView( + SizedBox( + height: 136.0, + child: ListView.builder( + primary: false, scrollDirection: Axis.horizontal, - child: Row(children: [ - for (final artist in results.artists!) - ArtistTile( + itemCount: results.artists!.length, + itemBuilder: (context, index) { + final artist = results.artists![index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: ArtistTile( artist, onTap: () { cache.addToSearchHistory(artist); @@ -566,7 +572,10 @@ class SearchResultsScreen extends StatelessWidget { m.defaultArtistMenu(artist, details: details); }, ), - ])), + ); + }, + ), + ), const FreezerDivider() ], if (results.playlists != null && diff --git a/lib/ui/tiles.dart b/lib/ui/tiles.dart index 56bbaee..e6555a0 100644 --- a/lib/ui/tiles.dart +++ b/lib/ui/tiles.dart @@ -14,21 +14,6 @@ import 'dart:async'; typedef SecondaryTapCallback = void Function(TapDownDetails?); -class WrapSecondaryAction extends StatelessWidget { - final SecondaryTapCallback? onSecondaryTapDown; - final Widget child; - const WrapSecondaryAction( - {super.key, this.onSecondaryTapDown, required this.child}); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onSecondaryTapDown: onSecondaryTapDown, - child: child, - ); - } -} - VoidCallback? normalizeSecondary(SecondaryTapCallback? callback) { if (callback == null) return null; @@ -75,7 +60,7 @@ class TrackTile extends StatelessWidget { title: track.title!, artist: track.artistString, artUri: track.albumArt!.thumb, - explicit: track.explicit!, + explicit: track.explicit ?? false, durationString: track.durationString, onSecondary: onSecondary, onTap: onTap, @@ -91,7 +76,7 @@ class TrackTile extends StatelessWidget { TrackTile( trackId: mediaItem.id, title: mediaItem.title, - artist: mediaItem.artist!, + artist: mediaItem.artist ?? '', artUri: mediaItem.extras!['thumb'], explicit: false, durationString: Track.durationAsString(mediaItem.duration!), @@ -103,7 +88,7 @@ class TrackTile extends StatelessWidget { @override Widget build(BuildContext context) { - return WrapSecondaryAction( + return GestureDetector( onSecondaryTapDown: onSecondary, child: ListTile( title: StreamBuilder( @@ -188,7 +173,7 @@ class AlbumTile extends StatelessWidget { @override Widget build(BuildContext context) { - return WrapSecondaryAction( + return GestureDetector( onSecondaryTapDown: onSecondary, child: ListTile( title: Text( @@ -228,13 +213,13 @@ class ArtistTile extends StatelessWidget { onLongPress: normalizeSecondary(onSecondary), onSecondaryTapDown: onSecondary, child: Column(mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 4), + const SizedBox(height: 4.0), CachedImage( url: artist!.picture!.thumb, circular: true, width: 100, ), - const SizedBox(height: 8), + const SizedBox(height: 8.0), Text( artist!.name!, maxLines: 1, @@ -269,7 +254,7 @@ class PlaylistTile extends StatelessWidget { @override Widget build(BuildContext context) { - return WrapSecondaryAction( + return GestureDetector( onSecondaryTapDown: onSecondary, child: ListTile( title: Text( @@ -323,59 +308,63 @@ class ArtistHorizontalTile extends StatelessWidget { class PlaylistCardTile extends StatelessWidget { final Playlist? playlist; - final Function? onTap; - final Function? onHold; - const PlaylistCardTile(this.playlist, {super.key, this.onTap, this.onHold}); + final VoidCallback? onTap; + final SecondaryTapCallback? onSecondary; + const PlaylistCardTile(this.playlist, + {super.key, this.onTap, this.onSecondary}); @override Widget build(BuildContext context) { - return SizedBox( - height: 180.0, - child: InkWell( - onTap: onTap as void Function()?, - onLongPress: onHold as void Function()?, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - CachedImage( - url: playlist!.image!.thumb, - width: 128.0, - height: 128.0, - rounded: true, - ), - Positioned( - bottom: 8.0, - left: 8.0, - child: PlayItemButton( - onTap: () async { - final Playlist fullPlaylist = - await deezerAPI.fullPlaylist(playlist!.id); - await playerHelper.playFromPlaylist(fullPlaylist); - }, - )) - ], + return GestureDetector( + onSecondaryTapDown: onSecondary, + child: SizedBox( + height: 180.0, + child: InkWell( + onTap: onTap, + onLongPress: normalizeSecondary(onSecondary), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Stack( + children: [ + CachedImage( + url: playlist!.image!.thumb, + width: 128.0, + height: 128.0, + rounded: true, + ), + Positioned( + bottom: 8.0, + left: 8.0, + child: PlayItemButton( + onTap: () async { + final Playlist fullPlaylist = + await deezerAPI.fullPlaylist(playlist!.id); + await playerHelper.playFromPlaylist(fullPlaylist); + }, + )) + ], + ), ), - ), - const SizedBox(height: 2.0), - SizedBox( - width: 144, - child: Text( - playlist!.title!, - maxLines: 1, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 14.0), + const SizedBox(height: 2.0), + SizedBox( + width: 144, + child: Text( + playlist!.title!, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14.0), + ), ), - ), - const SizedBox( - height: 4.0, - ) - ], - ), - )); + const SizedBox( + height: 4.0, + ) + ], + ), + )), + ); } } @@ -794,18 +783,19 @@ class ShowTile extends StatelessWidget { class ShowEpisodeTile extends StatelessWidget { final ShowEpisode episode; - final Function? onTap; - final Function? onHold; + final VoidCallback? onTap; + final SecondaryTapCallback? onSecondary; final Widget? trailing; const ShowEpisodeTile(this.episode, - {super.key, this.onTap, this.onHold, this.trailing}); + {super.key, this.onTap, this.onSecondary, this.trailing}); @override Widget build(BuildContext context) { return InkWell( - onLongPress: onHold as void Function()?, - onTap: onTap as void Function()?, + onTap: onTap, + onLongPress: normalizeSecondary(onSecondary), + onSecondaryTapDown: onSecondary, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -817,7 +807,7 @@ class ShowEpisodeTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text( episode.description!, - maxLines: 2, + maxLines: 10, overflow: TextOverflow.ellipsis, style: TextStyle( color: Theme.of(context) diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 897d1fc..5d470b8 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -123,6 +123,9 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) COMPONENT Runtime) endforeach(bundled_library) +# add app icon +install(FILES "app_icon.ico" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}") + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/linux/app_icon.ico b/linux/app_icon.ico new file mode 100644 index 0000000..8fbdc3d Binary files /dev/null and b/linux/app_icon.ico differ diff --git a/linux/my_application.cc b/linux/my_application.cc index 45b6a07..503ff0e 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -7,17 +7,19 @@ #include "flutter/generated_plugin_registrant.h" -struct _MyApplication { +struct _MyApplication +{ GtkApplication parent_instance; - char** dart_entrypoint_arguments; + char **dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = +static void my_application_activate(GApplication *application) +{ + MyApplication *self = MY_APPLICATION(application); + GtkWindow *window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used @@ -29,22 +31,29 @@ static void my_application_activate(GApplication* application) { // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + GdkScreen *screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) + { + const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) + { use_header_bar = FALSE; } } #endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + // set icon + gtk_window_set_icon_from_file(window, "data/app_icon.ico", NULL); + if (use_header_bar) + { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "freezer"); + gtk_header_bar_set_title(header_bar, "Freezer"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "freezer"); + } + else + { + gtk_window_set_title(window, "Freezer"); } gtk_window_set_default_size(window, 1280, 720); @@ -53,7 +62,7 @@ static void my_application_activate(GApplication* application) { g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - FlView* view = fl_view_new(project); + FlView *view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); @@ -63,16 +72,18 @@ static void my_application_activate(GApplication* application) { } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); +static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) +{ + MyApplication *self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; + if (!g_application_register(application, nullptr, &error)) + { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); @@ -82,21 +93,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch } // Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); +static void my_application_dispose(GObject *object) +{ + MyApplication *self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } -static void my_application_class_init(MyApplicationClass* klass) { +static void my_application_class_init(MyApplicationClass *klass) +{ G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } -static void my_application_init(MyApplication* self) {} +static void my_application_init(MyApplication *self) {} -MyApplication* my_application_new() { +MyApplication *my_application_new() +{ return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, diff --git a/pubspec.lock b/pubspec.lock index b4422b9..f0f863b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -350,10 +350,10 @@ packages: dependency: "direct main" description: name: dynamic_color - sha256: "96bff3df72e3d428bda2b874c7a521e8c86f592cae626ea594922fcc8d166e0c" + sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f" url: "https://pub.dev" source: hosted - version: "1.6.7" + version: "1.6.8" encrypt: dependency: "direct main" description: @@ -764,10 +764,10 @@ packages: description: path: "." ref: HEAD - resolved-ref: "8ccec63c67c0c206c6df3570e46f60b2a45dbb24" + resolved-ref: dcb7d1aa74a96b3dd892b3f65ec83f5d77352dfa url: "https://github.com/Pato05/just_audio_media_kit.git" source: git - version: "0.0.1" + version: "1.0.0" just_audio_platform_interface: dependency: transitive description: @@ -828,10 +828,10 @@ packages: dependency: transitive description: name: media_kit - sha256: "1283b500341d41f033478706204a2b4ae2612e9b331c934bc4fad8c4bb869f6d" + sha256: "3dffc6d0c19117d51fbc42a7f89612e0595665800a596289ab7a80bdd93e0ad1" url: "https://pub.dev" source: hosted - version: "1.1.8+2" + version: "1.1.9" media_kit_libs_linux: dependency: transitive description: @@ -1140,10 +1140,10 @@ packages: dependency: transitive description: name: quick_actions_android - sha256: f2ddc2c0cc5c001e87e62f6de06da18ebc75c6a06d26750f6f12276841c1585c + sha256: df67c20583e05f5038a24c47bfa1b7b2977703ec2d162663017c5f9ef8707699 url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" quick_actions_ios: dependency: transitive description: @@ -1197,18 +1197,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "2dafa6c1f8d8ee67b0e0587881947b78baab4671b7c08792cf91279e7ac14192" + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.2.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" shelf: dependency: transitive description: @@ -1514,10 +1514,10 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "268e56b9c63f850406f54e9acb2a7d2ddf83c26c8ff9e7a125a96c3a513bf65f" + sha256: f45a6c03aa3f8322e0a9d7f4a0482721c8789cb41d555407367650b8f9c26018 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" wakelock_plus_platform_interface: dependency: transitive description: diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index b9ad76d..ecca0bb 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -6,10 +6,12 @@ #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { + _In_ wchar_t *command_line, _In_ int show_command) +{ // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) + { CreateAndAttachConsole(); } @@ -27,13 +29,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.Create(L"freezer", origin, size)) { + if (!window.Create(L"Freezer", origin, size)) + { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { + while (::GetMessage(&msg, nullptr, 0, 0)) + { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..8fbdc3d 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ