right click support

add app-icon on desktop
This commit is contained in:
Pato05 2023-10-17 16:03:39 +02:00
parent 4c46399f9a
commit b629998416
No known key found for this signature in database
GPG key ID: F53CA394104BA0CB
18 changed files with 464 additions and 376 deletions

View file

@ -12,6 +12,7 @@ import 'package:freezer/ui/android_auto.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:connectivity_plus/connectivity_plus.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:logging/logging.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -432,6 +433,10 @@ class AudioPlayerTask extends BaseAudioHandler {
} }
Future<void> _init(AudioPlayerTaskInitArguments initArgs) async { Future<void> _init(AudioPlayerTaskInitArguments initArgs) async {
// Linux/Windows specific options
JustAudioMediaKit.title = 'Freezer';
JustAudioMediaKit.protocolWhitelist = const ['http'];
_deezerAPI = initArgs.deezerAPI; _deezerAPI = initArgs.deezerAPI;
_androidAuto = AndroidAuto(deezerAPI: _deezerAPI); _androidAuto = AndroidAuto(deezerAPI: _deezerAPI);
_shouldLogTracks = initArgs.logListen; _shouldLogTracks = initArgs.logListen;

View file

@ -7,6 +7,8 @@ class FadePageRoute<T> extends BasicPageRoute<T> {
final bool barrierDismissible; final bool barrierDismissible;
@override @override
final Color? barrierColor; final Color? barrierColor;
@override
final bool opaque;
final WidgetBuilder builder; final WidgetBuilder builder;
final bool blur; final bool blur;
@ -18,6 +20,7 @@ class FadePageRoute<T> extends BasicPageRoute<T> {
super.settings, super.settings,
this.barrierColor, this.barrierColor,
this.barrierDismissible = false, this.barrierDismissible = false,
this.opaque = true,
}); });
@override @override

View file

@ -147,7 +147,8 @@ class ZoomableImage extends StatelessWidget {
Navigator.of(context).push(FadePageRoute( Navigator.of(context).push(FadePageRoute(
builder: (context) => builder: (context) =>
ZoomableImageRoute(imageUrl: url, heroKey: _key), ZoomableImageRoute(imageUrl: url, heroKey: _key),
barrierDismissible: true)); barrierDismissible: true,
opaque: false));
}, },
); );
} }
@ -163,7 +164,7 @@ class ZoomableImageRoute extends StatefulWidget {
} }
class _ZoomableImageRouteState extends State<ZoomableImageRoute> { class _ZoomableImageRouteState extends State<ZoomableImageRoute> {
bool photoViewOpened = false; bool photoViewOpened = true;
final controller = PhotoViewController(); final controller = PhotoViewController();
final _focusNode = FocusScopeNode(); final _focusNode = FocusScopeNode();

View file

@ -73,15 +73,13 @@ class _AlbumDetailsState extends State<AlbumDetails> {
: ListView( : ListView(
children: <Widget>[ children: <Widget>[
//Album art, title, artists //Album art, title, artists
Container( Column(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints.loose( constraints: BoxConstraints.loose(
MediaQuery.of(context).size / 3), MediaQuery.of(context).size / 2.5),
child: ZoomableImage( child: ZoomableImage(
url: album!.art!.full, url: album!.art!.full,
rounded: true, rounded: true,
@ -118,7 +116,6 @@ class _AlbumDetailsState extends State<AlbumDetails> {
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
], ],
), ),
),
const FreezerDivider(), const FreezerDivider(),
//Details //Details
Row( Row(
@ -355,14 +352,15 @@ class _ArtistDetailsState extends State<ArtistDetails> {
const SizedBox(height: 4.0), const SizedBox(height: 4.0),
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SizedBox( child: ConstrainedBox(
height: MediaQuery.of(context).size.height / 3, constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 3),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Flexible( Flexible(
child: ZoomableImage( child: ZoomableImage(
url: widget.artist.picture!.full, url: artist.picture!.full,
rounded: true, rounded: true,
), ),
), ),
@ -1090,12 +1088,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
}, onSecondary: (details) { }, onSecondary: (details) {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultTrackMenu(t, details: details, options: [ m.defaultTrackMenu(t, details: details, options: [
(playlist!.user!.id == deezerAPI.userId) if (playlist!.user!.id == deezerAPI.userId)
? m.removeFromPlaylist(t, playlist) m.removeFromPlaylist(t, playlist)
: const SizedBox(
width: 0,
height: 0,
)
]); ]);
}); });
}), }),
@ -1128,9 +1122,11 @@ class _MakePlaylistOfflineState extends State<MakePlaylistOffline> {
@override @override
void initState() { void initState() {
downloadManager.checkOffline(playlist: widget.playlist).then((v) { downloadManager.checkOffline(playlist: widget.playlist).then((v) {
if (mounted) {
setState(() { setState(() {
_offline = v; _offline = v;
}); });
}
}); });
super.initState(); super.initState();
} }
@ -1221,32 +1217,38 @@ class _ShowScreenState extends State<ShowScreen> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 3),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
CachedImage( Flexible(
child: AspectRatio(
aspectRatio: 1.0,
child: CachedImage(
url: _show!.art!.full, url: _show!.art!.full,
rounded: true, rounded: true,
width: MediaQuery.of(context).size.width / 2 - 16, ),
),
), ),
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width / 2 - 16, width: min(MediaQuery.of(context).size.width / 16, 60.0)),
Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(_show!.name!, Text(_show!.name!,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle( style: const TextStyle(
fontSize: 20.0, fontWeight: FontWeight.bold)), fontSize: 20.0, fontWeight: FontWeight.bold)),
Container(height: 8.0), const SizedBox(height: 16.0),
Text( Text(
_show!.description!, _show!.description!,
maxLines: 6, maxLines: 6,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16.0), style: const TextStyle(fontSize: 16.0),
) )
], ],
@ -1255,21 +1257,15 @@ class _ShowScreenState extends State<ShowScreen> {
], ],
), ),
), ),
Container(height: 4.0), ),
const SizedBox(height: 4.0),
const FreezerDivider(), const FreezerDivider(),
//Error //Error
if (_error) const ErrorScreen(), if (_error) const ErrorScreen(),
//Loading //Loading
if (_loading) if (_loading) const Center(child: CircularProgressIndicator()),
const Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
),
),
//Data //Data
if (!_loading && !_error) if (!_loading && !_error)
@ -1277,16 +1273,10 @@ class _ShowScreenState extends State<ShowScreen> {
ShowEpisode e = _episodes![i]; ShowEpisode e = _episodes![i];
return ShowEpisodeTile( return ShowEpisodeTile(
e, e,
trailing: IconButton( onSecondary: (details) {
icon: Icon(
Icons.more_vert,
semanticLabel: "Options".i18n,
),
onPressed: () {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultShowEpisodeMenu(_show!, e); m.defaultShowEpisodeMenu(_show!, e, details: details);
}, },
),
onTap: () async { onTap: () async {
await playerHelper.playShowEpisode(_show!, _episodes!, await playerHelper.playShowEpisode(_show!, _episodes!,
index: i); index: i);

View file

@ -340,9 +340,9 @@ class HomePageItemWidget extends StatelessWidget {
Navigator.of(context) Navigator.of(context)
.pushRoute(builder: (context) => PlaylistDetails(item.value)); .pushRoute(builder: (context) => PlaylistDetails(item.value));
}, },
onHold: () { onSecondary: (details) {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultPlaylistMenu(item.value); m.defaultPlaylistMenu(item.value, details: details);
}, },
); );
case HomePageItemType.CHANNEL: case HomePageItemType.CHANNEL:

View file

@ -1217,6 +1217,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultTrackMenu(t, details: details); m.defaultTrackMenu(t, details: details);
}, },
checkTrackOffline: false,
); );
}, },
)), )),

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:ffi';
import 'package:freezer/main.dart'; import 'package:freezer/main.dart';
import 'package:freezer/ui/player_bar.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 { class MenuSheet {
BuildContext context; BuildContext context;
Function? navigateCallback; Function? navigateCallback;
MenuSheet(this.context, {this.navigateCallback}); MenuSheet(this.context, {this.navigateCallback});
void _showContextMenu(List<MenuSheetOption> 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 // DEFAULT
//=================== //===================
void show(List<Widget> options) { void show(List<MenuSheetOption> options, {TapDownDetails? details}) {
if (details != null) {
_showContextMenu(options, details: details);
return;
}
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: false, // true, isScrollControlled: false, // true,
context: context, context: context,
@ -117,7 +156,14 @@ class MenuSheet {
: 350, : 350,
), ),
child: SingleChildScrollView( 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 // TRACK
//=================== //===================
void showWithTrack(Track track, List<Widget> options) { void showWithTrack(Track track, List<MenuSheetOption> options,
{TapDownDetails? details}) {
if (details != null) {
_showContextMenu(options, details: details);
return;
}
showModalBottomSheet( showModalBottomSheet(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
context: context, context: context,
@ -154,7 +206,14 @@ class MenuSheet {
pinned: true, pinned: true,
delegate: SliverTrackPersistentHeader(track, delegate: SliverTrackPersistentHeader(track,
extent: 128.0 + 16.0 + 16.0)), 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,11 +224,13 @@ class MenuSheet {
//Default track options //Default track options
void defaultTrackMenu( void defaultTrackMenu(
Track track, { Track track, {
List<Widget> options = const [], List<MenuSheetOption> options = const [],
Function? onRemove, Function? onRemove,
TapDownDetails? details, TapDownDetails? details,
}) { }) {
showWithTrack(track, [ showWithTrack(
track,
<MenuSheetOption>[
addToQueueNext(track), addToQueueNext(track),
addToQueue(track), addToQueue(track),
(cache.checkTrackFavorite(track)) (cache.checkTrackFavorite(track))
@ -184,34 +245,32 @@ class MenuSheet {
...List.generate( ...List.generate(
track.artists!.length, (i) => showArtist(track.artists![i])), track.artists!.length, (i) => showArtist(track.artists![i])),
...options ...options
]); ],
details: details);
} }
//=================== //===================
// TRACK OPTIONS // TRACK OPTIONS
//=================== //===================
Widget addToQueueNext(Track t) => ListTile( MenuSheetOption addToQueueNext(Track t) =>
title: Text('Play next'.i18n), MenuSheetOption(Text('Play next'.i18n),
leading: const Icon(Icons.playlist_play), icon: const Icon(Icons.playlist_play), onTap: () async {
onTap: () async {
//-1 = next //-1 = next
await audioHandler.insertQueueItem(-1, await t.toMediaItem()); await audioHandler.insertQueueItem(-1, await t.toMediaItem());
_close(); _close();
}); });
Widget addToQueue(Track t) => ListTile( MenuSheetOption addToQueue(Track t) =>
title: Text('Add to queue'.i18n), MenuSheetOption(Text('Add to queue'.i18n),
leading: const Icon(Icons.playlist_add), icon: const Icon(Icons.playlist_add), onTap: () async {
onTap: () async {
await audioHandler.addQueueItem(await t.toMediaItem()); await audioHandler.addQueueItem(await t.toMediaItem());
_close(); _close();
}); });
Widget addTrackFavorite(Track t) => ListTile( MenuSheetOption addTrackFavorite(Track t) =>
title: Text('Add track to favorites'.i18n), MenuSheetOption(Text('Add track to favorites'.i18n),
leading: const Icon(Icons.favorite), icon: const Icon(Icons.favorite), onTap: () async {
onTap: () async {
await deezerAPI.addFavoriteTrack(t.id); await deezerAPI.addFavoriteTrack(t.id);
//Make track offline, if favorites are offline //Make track offline, if favorites are offline
Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!); Playlist p = Playlist(id: deezerAPI.favoritesPlaylistId!);
@ -225,9 +284,9 @@ class MenuSheet {
_close(); _close();
}); });
Widget downloadTrack(Track t) => ListTile( MenuSheetOption downloadTrack(Track t) => MenuSheetOption(
title: Text('Download'.i18n), Text('Download'.i18n),
leading: const Icon(Icons.file_download), icon: const Icon(Icons.file_download),
onTap: () async { onTap: () async {
if (await downloadManager.addOfflineTrack(t, if (await downloadManager.addOfflineTrack(t,
private: false, context: context, isSingleton: true) != private: false, context: context, isSingleton: true) !=
@ -236,9 +295,9 @@ class MenuSheet {
}, },
); );
Widget addToPlaylist(Track t) => ListTile( MenuSheetOption addToPlaylist(Track t) => MenuSheetOption(
title: Text('Add to playlist'.i18n), Text('Add to playlist'.i18n),
leading: const Icon(Icons.playlist_add), icon: const Icon(Icons.playlist_add),
onTap: () async { onTap: () async {
//Show dialog to pick playlist //Show dialog to pick playlist
await showDialog( await showDialog(
@ -260,9 +319,9 @@ class MenuSheet {
}, },
); );
Widget removeFromPlaylist(Track t, Playlist? p) => ListTile( MenuSheetOption removeFromPlaylist(Track t, Playlist? p) => MenuSheetOption(
title: Text('Remove from playlist'.i18n), Text('Remove from playlist'.i18n),
leading: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onTap: () async { onTap: () async {
await deezerAPI.removeFromPlaylist(t.id, p!.id); await deezerAPI.removeFromPlaylist(t.id, p!.id);
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
@ -271,9 +330,9 @@ class MenuSheet {
}, },
); );
Widget removeFavoriteTrack(Track t, {onUpdate}) => ListTile( MenuSheetOption removeFavoriteTrack(Track t, {onUpdate}) => MenuSheetOption(
title: Text('Remove favorite'.i18n), Text('Remove favorite'.i18n),
leading: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onTap: () async { onTap: () async {
await deezerAPI.removeFavorite(t.id); await deezerAPI.removeFavorite(t.id);
//Check if favorites playlist is offline, update it //Check if favorites playlist is offline, update it
@ -282,9 +341,7 @@ class MenuSheet {
await downloadManager.addOfflinePlaylist(p); await downloadManager.addOfflinePlaylist(p);
} }
//Remove from cache //Remove from cache
if (cache.libraryTracks != null) { cache.libraryTracks.removeWhere((i) => i == t.id);
cache.libraryTracks!.removeWhere((i) => i == t.id);
}
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.snack('Track removed from library'.i18n); .snack('Track removed from library'.i18n);
if (onUpdate != null) onUpdate(); if (onUpdate != null) onUpdate();
@ -293,13 +350,13 @@ class MenuSheet {
); );
//Redirect to artist page (ie from track) //Redirect to artist page (ie from track)
Widget showArtist(Artist a) => ListTile( MenuSheetOption showArtist(Artist a) => MenuSheetOption(
title: Text( Text(
'${'Go to'.i18n} ${a.name}', '${'Go to'.i18n} ${a.name}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
leading: const Icon(Icons.recent_actors), icon: const Icon(Icons.recent_actors),
onTap: () { onTap: () {
_close(); _close();
navigatorKey.currentState! navigatorKey.currentState!
@ -311,13 +368,13 @@ class MenuSheet {
}, },
); );
Widget showAlbum(Album a) => ListTile( MenuSheetOption showAlbum(Album a) => MenuSheetOption(
title: Text( Text(
'${'Go to'.i18n} ${a.title}', '${'Go to'.i18n} ${a.title}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
leading: const Icon(Icons.album), icon: const Icon(Icons.album),
onTap: () { onTap: () {
_close(); _close();
navigatorKey.currentState! navigatorKey.currentState!
@ -329,24 +386,24 @@ class MenuSheet {
}, },
); );
Widget playMix(Track track) => ListTile( MenuSheetOption playMix(Track track) => MenuSheetOption(
title: Text('Play mix'.i18n), Text('Play mix'.i18n),
leading: const Icon(Icons.online_prediction), icon: const Icon(Icons.online_prediction),
onTap: () async { onTap: () async {
playerHelper.playMix(track.id, track.title!); playerHelper.playMix(track.id, track.title!);
_close(); _close();
}, },
); );
Widget offlineTrack(Track track) => FutureBuilder( MenuSheetOption offlineTrack(Track track) => MenuSheetOption(
FutureBuilder(
future: downloadManager.checkOffline(track: track), future: downloadManager.checkOffline(track: track),
builder: (context, snapshot) { builder: (context, snapshot) {
bool isOffline = snapshot.data ?? track.offline ?? false; bool isOffline = snapshot.data ?? track.offline ?? false;
return ListTile( return Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n);
title: Text(isOffline ? 'Remove offline'.i18n : 'Offline'.i18n), }),
leading: const Icon(Icons.offline_pin), icon: const Icon(Icons.offline_pin), onTap: () async {
onTap: () async { if (await downloadManager.checkOffline(track: track)) {
if (isOffline) {
await downloadManager.removeOfflineTracks([track]); await downloadManager.removeOfflineTracks([track]);
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.snack("Track removed from offline!".i18n); .snack("Track removed from offline!".i18n);
@ -355,10 +412,7 @@ class MenuSheet {
private: true, context: context); private: true, context: context);
} }
_close(); _close();
}, });
);
},
);
//=================== //===================
// ALBUM // ALBUM
@ -366,7 +420,7 @@ class MenuSheet {
//Default album options //Default album options
void defaultAlbumMenu(Album album, void defaultAlbumMenu(Album album,
{List<Widget> options = const [], {List<MenuSheetOption> options = const [],
Function? onRemove, Function? onRemove,
TapDownDetails? details}) { TapDownDetails? details}) {
show([ show([
@ -384,19 +438,18 @@ class MenuSheet {
// ALBUM OPTIONS // ALBUM OPTIONS
//=================== //===================
Widget downloadAlbum(Album a) => ListTile( MenuSheetOption downloadAlbum(Album a) =>
title: Text('Download'.i18n), MenuSheetOption(Text('Download'.i18n),
leading: const Icon(Icons.file_download), icon: const Icon(Icons.file_download), onTap: () async {
onTap: () async {
_close(); _close();
if (await downloadManager.addOfflineAlbum(a, if (await downloadManager.addOfflineAlbum(a,
private: false, context: context) != private: false, context: context) !=
false) showDownloadStartedToast(); false) showDownloadStartedToast();
}); });
Widget offlineAlbum(Album a) => ListTile( MenuSheetOption offlineAlbum(Album a) => MenuSheetOption(
title: Text('Make offline'.i18n), Text('Make offline'.i18n),
leading: const Icon(Icons.offline_pin), icon: const Icon(Icons.offline_pin),
onTap: () async { onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id); await deezerAPI.addFavoriteAlbum(a.id);
await downloadManager.addOfflineAlbum(a, private: true); await downloadManager.addOfflineAlbum(a, private: true);
@ -405,9 +458,9 @@ class MenuSheet {
}, },
); );
Widget libraryAlbum(Album a) => ListTile( MenuSheetOption libraryAlbum(Album a) => MenuSheetOption(
title: Text('Add to library'.i18n), Text('Add to library'.i18n),
leading: const Icon(Icons.library_music), icon: const Icon(Icons.library_music),
onTap: () async { onTap: () async {
await deezerAPI.addFavoriteAlbum(a.id); await deezerAPI.addFavoriteAlbum(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n); ScaffoldMessenger.of(context).snack('Added to library'.i18n);
@ -416,9 +469,9 @@ class MenuSheet {
); );
//Remove album from favorites //Remove album from favorites
Widget removeAlbum(Album a, {Function? onRemove}) => ListTile( MenuSheetOption removeAlbum(Album a, {Function? onRemove}) => MenuSheetOption(
title: Text('Remove album'.i18n), Text('Remove album'.i18n),
leading: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onTap: () async { onTap: () async {
await deezerAPI.removeAlbum(a.id); await deezerAPI.removeAlbum(a.id);
await downloadManager.removeOfflineAlbum(a.id); await downloadManager.removeOfflineAlbum(a.id);
@ -433,10 +486,10 @@ class MenuSheet {
//=================== //===================
void defaultArtistMenu(Artist artist, void defaultArtistMenu(Artist artist,
{List<Widget> options = const [], {List<MenuSheetOption> options = const [],
Function? onRemove, Function? onRemove,
TapDownDetails? details}) { TapDownDetails? details}) {
show([ show(details: details, [
artist.library! artist.library!
? removeArtist(artist, onRemove: onRemove) ? removeArtist(artist, onRemove: onRemove)
: favoriteArtist(artist), : favoriteArtist(artist),
@ -449,9 +502,10 @@ class MenuSheet {
// ARTIST OPTIONS // ARTIST OPTIONS
//=================== //===================
Widget removeArtist(Artist a, {Function? onRemove}) => ListTile( MenuSheetOption removeArtist(Artist a, {Function? onRemove}) =>
title: Text('Remove from favorites'.i18n), MenuSheetOption(
leading: const Icon(Icons.delete), Text('Remove from favorites'.i18n),
icon: const Icon(Icons.delete),
onTap: () async { onTap: () async {
await deezerAPI.removeArtist(a.id); await deezerAPI.removeArtist(a.id);
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
@ -461,9 +515,9 @@ class MenuSheet {
}, },
); );
Widget favoriteArtist(Artist a) => ListTile( MenuSheetOption favoriteArtist(Artist a) => MenuSheetOption(
title: Text('Add to favorites'.i18n), Text('Add to favorites'.i18n),
leading: const Icon(Icons.favorite), icon: const Icon(Icons.favorite),
onTap: () async { onTap: () async {
await deezerAPI.addFavoriteArtist(a.id); await deezerAPI.addFavoriteArtist(a.id);
ScaffoldMessenger.of(context).snack('Added to library'.i18n); ScaffoldMessenger.of(context).snack('Added to library'.i18n);
@ -476,11 +530,11 @@ class MenuSheet {
//=================== //===================
void defaultPlaylistMenu(Playlist playlist, void defaultPlaylistMenu(Playlist playlist,
{List<Widget> options = const [], {List<MenuSheetOption> options = const [],
Function? onRemove, Function? onRemove,
Function? onUpdate, Function? onUpdate,
TapDownDetails? details}) { TapDownDetails? details}) {
show([ show(details: details, [
if (playlist.library != null) if (playlist.library != null)
playlist.library! playlist.library!
? removePlaylistLibrary(playlist, onRemove: onRemove) ? removePlaylistLibrary(playlist, onRemove: onRemove)
@ -498,9 +552,10 @@ class MenuSheet {
// PLAYLIST OPTIONS // PLAYLIST OPTIONS
//=================== //===================
Widget removePlaylistLibrary(Playlist p, {Function? onRemove}) => ListTile( MenuSheetOption removePlaylistLibrary(Playlist p, {Function? onRemove}) =>
title: Text('Remove from library'.i18n), MenuSheetOption(
leading: const Icon(Icons.delete), Text('Remove from library'.i18n),
icon: const Icon(Icons.delete),
onTap: () async { onTap: () async {
if (p.user!.id!.trim() == deezerAPI.userId) { if (p.user!.id!.trim() == deezerAPI.userId) {
//Delete playlist if own //Delete playlist if own
@ -515,9 +570,9 @@ class MenuSheet {
}, },
); );
Widget addPlaylistLibrary(Playlist p) => ListTile( MenuSheetOption addPlaylistLibrary(Playlist p) => MenuSheetOption(
title: Text('Add playlist to library'.i18n), Text('Add playlist to library'.i18n),
leading: const Icon(Icons.favorite), icon: const Icon(Icons.favorite),
onTap: () async { onTap: () async {
await deezerAPI.addPlaylist(p.id); await deezerAPI.addPlaylist(p.id);
ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n); ScaffoldMessenger.of(context).snack('Added playlist to library'.i18n);
@ -525,9 +580,9 @@ class MenuSheet {
}, },
); );
Widget addPlaylistOffline(Playlist p) => ListTile( MenuSheetOption addPlaylistOffline(Playlist p) => MenuSheetOption(
title: Text('Make playlist offline'.i18n), Text('Make playlist offline'.i18n),
leading: const Icon(Icons.offline_pin), icon: const Icon(Icons.offline_pin),
onTap: () async { onTap: () async {
//Add to library //Add to library
await deezerAPI.addPlaylist(p.id); await deezerAPI.addPlaylist(p.id);
@ -537,9 +592,9 @@ class MenuSheet {
}, },
); );
Widget downloadPlaylist(Playlist p) => ListTile( MenuSheetOption downloadPlaylist(Playlist p) => MenuSheetOption(
title: Text('Download playlist'.i18n), Text('Download playlist'.i18n),
leading: const Icon(Icons.file_download), icon: const Icon(Icons.file_download),
onTap: () async { onTap: () async {
_close(); _close();
if (await downloadManager.addOfflinePlaylist(p, if (await downloadManager.addOfflinePlaylist(p,
@ -548,9 +603,10 @@ class MenuSheet {
}, },
); );
Widget editPlaylist(Playlist p, {Function? onUpdate}) => ListTile( MenuSheetOption editPlaylist(Playlist p, {Function? onUpdate}) =>
title: Text('Edit playlist'.i18n), MenuSheetOption(
leading: const Icon(Icons.edit), Text('Edit playlist'.i18n),
icon: const Icon(Icons.edit),
onTap: () async { onTap: () async {
await showDialog( await showDialog(
context: context, context: context,
@ -565,8 +621,8 @@ class MenuSheet {
//=================== //===================
defaultShowEpisodeMenu(Show s, ShowEpisode e, defaultShowEpisodeMenu(Show s, ShowEpisode e,
{List<Widget> options = const []}) { {List<MenuSheetOption> options = const [], TapDownDetails? details}) {
show([ show(details: details, [
shareTile('episode', e.id), shareTile('episode', e.id),
shareShow(s.id), shareShow(s.id),
downloadExternalEpisode(e), downloadExternalEpisode(e),
@ -574,18 +630,18 @@ class MenuSheet {
]); ]);
} }
Widget shareShow(String? id) => ListTile( MenuSheetOption shareShow(String? id) => MenuSheetOption(
title: Text('Share show'.i18n), Text('Share show'.i18n),
leading: const Icon(Icons.share), icon: const Icon(Icons.share),
onTap: () async { onTap: () async {
Share.share('https://deezer.com/show/$id'); Share.share('https://deezer.com/show/$id');
}, },
); );
//Open direct download link in browser //Open direct download link in browser
Widget downloadExternalEpisode(ShowEpisode e) => ListTile( MenuSheetOption downloadExternalEpisode(ShowEpisode e) => MenuSheetOption(
title: Text('Download externally'.i18n), Text('Download externally'.i18n),
leading: const Icon(Icons.file_download), icon: const Icon(Icons.file_download),
onTap: () async { onTap: () async {
launchUrl(Uri.parse(e.url!)); launchUrl(Uri.parse(e.url!));
}, },
@ -608,17 +664,17 @@ class MenuSheet {
}); });
} }
Widget shareTile(String type, String? id) => ListTile( MenuSheetOption shareTile(String type, String? id) => MenuSheetOption(
title: Text('Share'.i18n), Text('Share'.i18n),
leading: const Icon(Icons.share), icon: const Icon(Icons.share),
onTap: () async { onTap: () async {
Share.share('https://deezer.com/$type/$id'); Share.share('https://deezer.com/$type/$id');
}, },
); );
Widget sleepTimer() => ListTile( MenuSheetOption sleepTimer() => MenuSheetOption(
title: Text('Sleep timer'.i18n), Text('Sleep timer'.i18n),
leading: const Icon(Icons.access_time), icon: const Icon(Icons.access_time),
onTap: () async { onTap: () async {
showDialog( showDialog(
context: context, context: context,
@ -628,9 +684,9 @@ class MenuSheet {
}, },
); );
Widget wakelock() => ListTile( MenuSheetOption wakelock() => MenuSheetOption(
title: Text('Keep the screen on'.i18n), Text('Keep the screen on'.i18n),
leading: const Icon(Icons.screen_lock_portrait), icon: const Icon(Icons.screen_lock_portrait),
onTap: () async { onTap: () async {
_close(); _close();
//Enable //Enable

View file

@ -237,7 +237,15 @@ class PlayerBar extends StatelessWidget {
if (snapshot.data == null) { if (snapshot.data == null) {
return Material( return Material(
child: ListTile( 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), title: Text('Nothing is currently playing'.i18n),
), ),
); );
@ -254,10 +262,12 @@ class PlayerBar extends StatelessWidget {
: image; : image;
return Material( return Material(
child: ListTile( child: ListTile(
dense: true,
tileColor: _backgroundColor, tileColor: _backgroundColor,
focusNode: focusNode, focusNode: focusNode,
contentPadding: visualDensity: VisualDensity.standard,
const EdgeInsets.symmetric(horizontal: 8.0), contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 0.0),
onTap: onTap, onTap: onTap,
leading: AnimatedSwitcher( leading: AnimatedSwitcher(
key: const ValueKey('player_bar_art_switcher'), key: const ValueKey('player_bar_art_switcher'),

View file

@ -1,3 +1,5 @@
// ignore_for_file: unused_import
import 'dart:ui'; import 'dart:ui';
import 'dart:async'; import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
@ -691,16 +693,14 @@ class _FavoriteButtonState extends State<FavoriteButton> {
icon: libraryIcon, icon: libraryIcon,
iconSize: widget.size, iconSize: widget.size,
onPressed: () async { onPressed: () async {
cache.libraryTracks ??= [];
if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) { if (cache.checkTrackFavorite(Track.fromMediaItem(mediaItem))) {
//Remove from library //Remove from library
setState(() => cache.libraryTracks!.remove(mediaItem.id)); setState(() => cache.libraryTracks.remove(mediaItem.id));
await deezerAPI.removeFavorite(mediaItem.id); await deezerAPI.removeFavorite(mediaItem.id);
await cache.save(); await cache.save();
} else { } else {
//Add //Add
setState(() => cache.libraryTracks!.add(mediaItem.id)); setState(() => cache.libraryTracks.add(mediaItem.id));
await deezerAPI.addFavoriteTrack(mediaItem.id); await deezerAPI.addFavoriteTrack(mediaItem.id);
await cache.save(); await cache.save();
} }
@ -840,6 +840,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
context, context,
FadePageRoute( FadePageRoute(
barrierDismissible: true, barrierDismissible: true,
opaque: false,
builder: (context) { builder: (context) {
final mediaItem = audioHandler.mediaItem.value!; final mediaItem = audioHandler.mediaItem.value!;
return ZoomableImageRoute( return ZoomableImageRoute(
@ -867,7 +868,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
onHorizontalDragDown: (_) => _userScroll = true, onHorizontalDragDown: (_) => _userScroll = true,
// delayed a bit, so to make sure that the page view updated. // delayed a bit, so to make sure that the page view updated.
onHorizontalDragEnd: (_) => Future.delayed( onHorizontalDragEnd: (_) => Future.delayed(
const Duration(milliseconds: 500), () => _userScroll = false), const Duration(milliseconds: 100), () => _userScroll = false),
child: StreamBuilder<List<MediaItem>>( child: StreamBuilder<List<MediaItem>>(
stream: audioHandler.queue, stream: audioHandler.queue,
initialData: audioHandler.queue.valueOrNull, initialData: audioHandler.queue.valueOrNull,

View file

@ -168,8 +168,9 @@ class _QueueListWidgetState extends State<QueueListWidget> {
} }
}); });
}, },
onSecondary: (_) => MenuSheet(context) onSecondary: (details) => MenuSheet(context).defaultTrackMenu(
.defaultTrackMenu(Track.fromMediaItem(mediaItem)), Track.fromMediaItem(mediaItem),
details: details),
checkTrackOffline: false, checkTrackOffline: false,
), ),
), ),

View file

@ -550,11 +550,17 @@ class SearchResultsScreen extends StatelessWidget {
), ),
), ),
const SizedBox(height: 4.0), const SizedBox(height: 4.0),
SingleChildScrollView( SizedBox(
height: 136.0,
child: ListView.builder(
primary: false,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row(children: [ itemCount: results.artists!.length,
for (final artist in results.artists!) itemBuilder: (context, index) {
ArtistTile( final artist = results.artists![index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ArtistTile(
artist, artist,
onTap: () { onTap: () {
cache.addToSearchHistory(artist); cache.addToSearchHistory(artist);
@ -566,7 +572,10 @@ class SearchResultsScreen extends StatelessWidget {
m.defaultArtistMenu(artist, details: details); m.defaultArtistMenu(artist, details: details);
}, },
), ),
])), );
},
),
),
const FreezerDivider() const FreezerDivider()
], ],
if (results.playlists != null && if (results.playlists != null &&

View file

@ -14,21 +14,6 @@ import 'dart:async';
typedef SecondaryTapCallback = void Function(TapDownDetails?); 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) { VoidCallback? normalizeSecondary(SecondaryTapCallback? callback) {
if (callback == null) return null; if (callback == null) return null;
@ -75,7 +60,7 @@ class TrackTile extends StatelessWidget {
title: track.title!, title: track.title!,
artist: track.artistString, artist: track.artistString,
artUri: track.albumArt!.thumb, artUri: track.albumArt!.thumb,
explicit: track.explicit!, explicit: track.explicit ?? false,
durationString: track.durationString, durationString: track.durationString,
onSecondary: onSecondary, onSecondary: onSecondary,
onTap: onTap, onTap: onTap,
@ -91,7 +76,7 @@ class TrackTile extends StatelessWidget {
TrackTile( TrackTile(
trackId: mediaItem.id, trackId: mediaItem.id,
title: mediaItem.title, title: mediaItem.title,
artist: mediaItem.artist!, artist: mediaItem.artist ?? '',
artUri: mediaItem.extras!['thumb'], artUri: mediaItem.extras!['thumb'],
explicit: false, explicit: false,
durationString: Track.durationAsString(mediaItem.duration!), durationString: Track.durationAsString(mediaItem.duration!),
@ -103,7 +88,7 @@ class TrackTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WrapSecondaryAction( return GestureDetector(
onSecondaryTapDown: onSecondary, onSecondaryTapDown: onSecondary,
child: ListTile( child: ListTile(
title: StreamBuilder<MediaItem?>( title: StreamBuilder<MediaItem?>(
@ -188,7 +173,7 @@ class AlbumTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WrapSecondaryAction( return GestureDetector(
onSecondaryTapDown: onSecondary, onSecondaryTapDown: onSecondary,
child: ListTile( child: ListTile(
title: Text( title: Text(
@ -228,13 +213,13 @@ class ArtistTile extends StatelessWidget {
onLongPress: normalizeSecondary(onSecondary), onLongPress: normalizeSecondary(onSecondary),
onSecondaryTapDown: onSecondary, onSecondaryTapDown: onSecondary,
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[ child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
const SizedBox(height: 4), const SizedBox(height: 4.0),
CachedImage( CachedImage(
url: artist!.picture!.thumb, url: artist!.picture!.thumb,
circular: true, circular: true,
width: 100, width: 100,
), ),
const SizedBox(height: 8), const SizedBox(height: 8.0),
Text( Text(
artist!.name!, artist!.name!,
maxLines: 1, maxLines: 1,
@ -269,7 +254,7 @@ class PlaylistTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WrapSecondaryAction( return GestureDetector(
onSecondaryTapDown: onSecondary, onSecondaryTapDown: onSecondary,
child: ListTile( child: ListTile(
title: Text( title: Text(
@ -323,17 +308,20 @@ class ArtistHorizontalTile extends StatelessWidget {
class PlaylistCardTile extends StatelessWidget { class PlaylistCardTile extends StatelessWidget {
final Playlist? playlist; final Playlist? playlist;
final Function? onTap; final VoidCallback? onTap;
final Function? onHold; final SecondaryTapCallback? onSecondary;
const PlaylistCardTile(this.playlist, {super.key, this.onTap, this.onHold}); const PlaylistCardTile(this.playlist,
{super.key, this.onTap, this.onSecondary});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return GestureDetector(
onSecondaryTapDown: onSecondary,
child: SizedBox(
height: 180.0, height: 180.0,
child: InkWell( child: InkWell(
onTap: onTap as void Function()?, onTap: onTap,
onLongPress: onHold as void Function()?, onLongPress: normalizeSecondary(onSecondary),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Padding( Padding(
@ -375,7 +363,8 @@ class PlaylistCardTile extends StatelessWidget {
) )
], ],
), ),
)); )),
);
} }
} }
@ -794,18 +783,19 @@ class ShowTile extends StatelessWidget {
class ShowEpisodeTile extends StatelessWidget { class ShowEpisodeTile extends StatelessWidget {
final ShowEpisode episode; final ShowEpisode episode;
final Function? onTap; final VoidCallback? onTap;
final Function? onHold; final SecondaryTapCallback? onSecondary;
final Widget? trailing; final Widget? trailing;
const ShowEpisodeTile(this.episode, const ShowEpisodeTile(this.episode,
{super.key, this.onTap, this.onHold, this.trailing}); {super.key, this.onTap, this.onSecondary, this.trailing});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
onLongPress: onHold as void Function()?, onTap: onTap,
onTap: onTap as void Function()?, onLongPress: normalizeSecondary(onSecondary),
onSecondaryTapDown: onSecondary,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -817,7 +807,7 @@ class ShowEpisodeTile extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text( child: Text(
episode.description!, episode.description!,
maxLines: 2, maxLines: 10,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)

View file

@ -123,6 +123,9 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime) COMPONENT Runtime)
endforeach(bundled_library) 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 # Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install. # from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets") set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

BIN
linux/app_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View file

@ -7,7 +7,8 @@
#include "flutter/generated_plugin_registrant.h" #include "flutter/generated_plugin_registrant.h"
struct _MyApplication { struct _MyApplication
{
GtkApplication parent_instance; GtkApplication parent_instance;
char **dart_entrypoint_arguments; char **dart_entrypoint_arguments;
}; };
@ -15,7 +16,8 @@ struct _MyApplication {
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate. // Implements GApplication::activate.
static void my_application_activate(GApplication* application) { static void my_application_activate(GApplication *application)
{
MyApplication *self = MY_APPLICATION(application); MyApplication *self = MY_APPLICATION(application);
GtkWindow *window = GtkWindow *window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
@ -30,21 +32,28 @@ static void my_application_activate(GApplication* application) {
gboolean use_header_bar = TRUE; gboolean use_header_bar = TRUE;
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
GdkScreen *screen = gtk_window_get_screen(window); GdkScreen *screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) { if (GDK_IS_X11_SCREEN(screen))
{
const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen);
if (g_strcmp0(wm_name, "GNOME Shell") != 0) { if (g_strcmp0(wm_name, "GNOME Shell") != 0)
{
use_header_bar = FALSE; use_header_bar = FALSE;
} }
} }
#endif #endif
if (use_header_bar) { // 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()); GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar)); 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_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 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); gtk_window_set_default_size(window, 1280, 720);
@ -63,13 +72,15 @@ static void my_application_activate(GApplication* application) {
} }
// Implements GApplication::local_command_line. // Implements GApplication::local_command_line.
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status)
{
MyApplication *self = MY_APPLICATION(application); MyApplication *self = MY_APPLICATION(application);
// Strip out the first argument as it is the binary name. // Strip out the first argument as it is the binary name.
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
if (!g_application_register(application, nullptr, &error)) { if (!g_application_register(application, nullptr, &error))
{
g_warning("Failed to register: %s", error->message); g_warning("Failed to register: %s", error->message);
*exit_status = 1; *exit_status = 1;
return TRUE; return TRUE;
@ -82,13 +93,15 @@ static gboolean my_application_local_command_line(GApplication* application, gch
} }
// Implements GObject::dispose. // Implements GObject::dispose.
static void my_application_dispose(GObject* object) { static void my_application_dispose(GObject *object)
{
MyApplication *self = MY_APPLICATION(object); MyApplication *self = MY_APPLICATION(object);
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 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)->activate = my_application_activate;
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
G_OBJECT_CLASS(klass)->dispose = my_application_dispose; G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
@ -96,7 +109,8 @@ static void my_application_class_init(MyApplicationClass* klass) {
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(), return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE, "flags", G_APPLICATION_NON_UNIQUE,

View file

@ -350,10 +350,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: "96bff3df72e3d428bda2b874c7a521e8c86f592cae626ea594922fcc8d166e0c" sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.7" version: "1.6.8"
encrypt: encrypt:
dependency: "direct main" dependency: "direct main"
description: description:
@ -764,10 +764,10 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: "8ccec63c67c0c206c6df3570e46f60b2a45dbb24" resolved-ref: dcb7d1aa74a96b3dd892b3f65ec83f5d77352dfa
url: "https://github.com/Pato05/just_audio_media_kit.git" url: "https://github.com/Pato05/just_audio_media_kit.git"
source: git source: git
version: "0.0.1" version: "1.0.0"
just_audio_platform_interface: just_audio_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -828,10 +828,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: media_kit name: media_kit
sha256: "1283b500341d41f033478706204a2b4ae2612e9b331c934bc4fad8c4bb869f6d" sha256: "3dffc6d0c19117d51fbc42a7f89612e0595665800a596289ab7a80bdd93e0ad1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.8+2" version: "1.1.9"
media_kit_libs_linux: media_kit_libs_linux:
dependency: transitive dependency: transitive
description: description:
@ -1140,10 +1140,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: quick_actions_android name: quick_actions_android
sha256: f2ddc2c0cc5c001e87e62f6de06da18ebc75c6a06d26750f6f12276841c1585c sha256: df67c20583e05f5038a24c47bfa1b7b2977703ec2d162663017c5f9ef8707699
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.9"
quick_actions_ios: quick_actions_ios:
dependency: transitive dependency: transitive
description: description:
@ -1197,18 +1197,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: "2dafa6c1f8d8ee67b0e0587881947b78baab4671b7c08792cf91279e7ac14192" sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.0" version: "7.2.1"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.3.1"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@ -1514,10 +1514,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "268e56b9c63f850406f54e9acb2a7d2ddf83c26c8ff9e7a125a96c3a513bf65f" sha256: f45a6c03aa3f8322e0a9d7f4a0482721c8789cb41d555407367650b8f9c26018
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.3"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:

View file

@ -6,10 +6,12 @@
#include "utils.h" #include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 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 // Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger. // new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
{
CreateAndAttachConsole(); CreateAndAttachConsole();
} }
@ -27,13 +29,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720); Win32Window::Size size(1280, 720);
if (!window.Create(L"freezer", origin, size)) { if (!window.Create(L"Freezer", origin, size))
{
return EXIT_FAILURE; return EXIT_FAILURE;
} }
window.SetQuitOnClose(true); window.SetQuitOnClose(true);
::MSG msg; ::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) { while (::GetMessage(&msg, nullptr, 0, 0))
{
::TranslateMessage(&msg); ::TranslateMessage(&msg);
::DispatchMessage(&msg); ::DispatchMessage(&msg);
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 264 KiB