right click support
add app-icon on desktop
This commit is contained in:
parent
4c46399f9a
commit
b629998416
|
@ -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<void> _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;
|
||||
|
|
|
@ -7,6 +7,8 @@ class FadePageRoute<T> extends BasicPageRoute<T> {
|
|||
final bool barrierDismissible;
|
||||
@override
|
||||
final Color? barrierColor;
|
||||
@override
|
||||
final bool opaque;
|
||||
|
||||
final WidgetBuilder builder;
|
||||
final bool blur;
|
||||
|
@ -18,6 +20,7 @@ class FadePageRoute<T> extends BasicPageRoute<T> {
|
|||
super.settings,
|
||||
this.barrierColor,
|
||||
this.barrierDismissible = false,
|
||||
this.opaque = true,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
|
@ -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<ZoomableImageRoute> {
|
||||
bool photoViewOpened = false;
|
||||
bool photoViewOpened = true;
|
||||
final controller = PhotoViewController();
|
||||
final _focusNode = FocusScopeNode();
|
||||
|
||||
|
|
|
@ -73,15 +73,13 @@ class _AlbumDetailsState extends State<AlbumDetails> {
|
|||
: ListView(
|
||||
children: <Widget>[
|
||||
//Album art, title, artists
|
||||
Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Column(
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 8.0),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints.loose(
|
||||
MediaQuery.of(context).size / 3),
|
||||
MediaQuery.of(context).size / 2.5),
|
||||
child: ZoomableImage(
|
||||
url: album!.art!.full,
|
||||
rounded: true,
|
||||
|
@ -118,7 +116,6 @@ class _AlbumDetailsState extends State<AlbumDetails> {
|
|||
const SizedBox(height: 8.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
const FreezerDivider(),
|
||||
//Details
|
||||
Row(
|
||||
|
@ -355,14 +352,15 @@ class _ArtistDetailsState extends State<ArtistDetails> {
|
|||
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: <Widget>[
|
||||
Flexible(
|
||||
child: ZoomableImage(
|
||||
url: widget.artist.picture!.full,
|
||||
url: artist.picture!.full,
|
||||
rounded: true,
|
||||
),
|
||||
),
|
||||
|
@ -1090,12 +1088,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
|
|||
}, 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<MakePlaylistOffline> {
|
|||
@override
|
||||
void initState() {
|
||||
downloadManager.checkOffline(playlist: widget.playlist).then((v) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_offline = v;
|
||||
});
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
@ -1221,32 +1217,38 @@ class _ShowScreenState extends State<ShowScreen> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 3),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
CachedImage(
|
||||
Flexible(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: CachedImage(
|
||||
url: _show!.art!.full,
|
||||
rounded: true,
|
||||
width: MediaQuery.of(context).size.width / 2 - 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width / 2 - 16,
|
||||
width: min(MediaQuery.of(context).size.width / 16, 60.0)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
_show!.description!,
|
||||
maxLines: 6,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
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(),
|
||||
|
||||
//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<ShowScreen> {
|
|||
ShowEpisode e = _episodes![i];
|
||||
return ShowEpisodeTile(
|
||||
e,
|
||||
trailing: IconButton(
|
||||
icon: Icon(
|
||||
Icons.more_vert,
|
||||
semanticLabel: "Options".i18n,
|
||||
),
|
||||
onPressed: () {
|
||||
onSecondary: (details) {
|
||||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultShowEpisodeMenu(_show!, e);
|
||||
m.defaultShowEpisodeMenu(_show!, e, details: details);
|
||||
},
|
||||
),
|
||||
onTap: () async {
|
||||
await playerHelper.playShowEpisode(_show!, _episodes!,
|
||||
index: i);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1217,6 +1217,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||
MenuSheet m = MenuSheet(context);
|
||||
m.defaultTrackMenu(t, details: details);
|
||||
},
|
||||
checkTrackOffline: false,
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
|
276
lib/ui/menu.dart
276
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<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
|
||||
//===================
|
||||
|
||||
void show(List<Widget> options) {
|
||||
void show(List<MenuSheetOption> 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<Widget> options) {
|
||||
void showWithTrack(Track track, List<MenuSheetOption> 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,11 +224,13 @@ class MenuSheet {
|
|||
//Default track options
|
||||
void defaultTrackMenu(
|
||||
Track track, {
|
||||
List<Widget> options = const [],
|
||||
List<MenuSheetOption> options = const [],
|
||||
Function? onRemove,
|
||||
TapDownDetails? details,
|
||||
}) {
|
||||
showWithTrack(track, [
|
||||
showWithTrack(
|
||||
track,
|
||||
<MenuSheetOption>[
|
||||
addToQueueNext(track),
|
||||
addToQueue(track),
|
||||
(cache.checkTrackFavorite(track))
|
||||
|
@ -184,34 +245,32 @@ class MenuSheet {
|
|||
...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,24 +386,24 @@ 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(
|
||||
MenuSheetOption offlineTrack(Track track) => MenuSheetOption(
|
||||
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) {
|
||||
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);
|
||||
|
@ -355,10 +412,7 @@ class MenuSheet {
|
|||
private: true, context: context);
|
||||
}
|
||||
_close();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
//===================
|
||||
// ALBUM
|
||||
|
@ -366,7 +420,7 @@ class MenuSheet {
|
|||
|
||||
//Default album options
|
||||
void defaultAlbumMenu(Album album,
|
||||
{List<Widget> options = const [],
|
||||
{List<MenuSheetOption> 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<Widget> options = const [],
|
||||
{List<MenuSheetOption> 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<Widget> options = const [],
|
||||
{List<MenuSheetOption> 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<Widget> options = const []}) {
|
||||
show([
|
||||
{List<MenuSheetOption> 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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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<FavoriteButton> {
|
|||
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<BigAlbumArt> {
|
|||
context,
|
||||
FadePageRoute(
|
||||
barrierDismissible: true,
|
||||
opaque: false,
|
||||
builder: (context) {
|
||||
final mediaItem = audioHandler.mediaItem.value!;
|
||||
return ZoomableImageRoute(
|
||||
|
@ -867,7 +868,7 @@ class _BigAlbumArtState extends State<BigAlbumArt> {
|
|||
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<List<MediaItem>>(
|
||||
stream: audioHandler.queue,
|
||||
initialData: audioHandler.queue.valueOrNull,
|
||||
|
|
|
@ -168,8 +168,9 @@ class _QueueListWidgetState extends State<QueueListWidget> {
|
|||
}
|
||||
});
|
||||
},
|
||||
onSecondary: (_) => MenuSheet(context)
|
||||
.defaultTrackMenu(Track.fromMediaItem(mediaItem)),
|
||||
onSecondary: (details) => MenuSheet(context).defaultTrackMenu(
|
||||
Track.fromMediaItem(mediaItem),
|
||||
details: details),
|
||||
checkTrackOffline: false,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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<MediaItem?>(
|
||||
|
@ -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: <Widget>[
|
||||
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,17 +308,20 @@ 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(
|
||||
return GestureDetector(
|
||||
onSecondaryTapDown: onSecondary,
|
||||
child: SizedBox(
|
||||
height: 180.0,
|
||||
child: InkWell(
|
||||
onTap: onTap as void Function()?,
|
||||
onLongPress: onHold as void Function()?,
|
||||
onTap: onTap,
|
||||
onLongPress: normalizeSecondary(onSecondary),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
|
@ -375,7 +363,8 @@ class PlaylistCardTile extends StatelessWidget {
|
|||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
BIN
linux/app_icon.ico
Normal file
BIN
linux/app_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
|
@ -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,13 +72,15 @@ 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)) {
|
||||
if (!g_application_register(application, nullptr, &error))
|
||||
{
|
||||
g_warning("Failed to register: %s", error->message);
|
||||
*exit_status = 1;
|
||||
return TRUE;
|
||||
|
@ -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,
|
||||
|
|
28
pubspec.lock
28
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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 264 KiB |
Loading…
Reference in a new issue