diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0fd2ccd..d0340e9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,23 +5,6 @@ on: branches: [master] jobs: - build-web: - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v4 - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - cache: true - - run: flutter pub get - - run: flutter build web --release --base-href=/ - - name: Archive production artifacts - uses: actions/upload-artifact@v4 - with: - name: build-output-web - path: build/web build-exe: runs-on: windows-latest steps: diff --git a/lib/main.dart b/lib/main.dart index 914ed2b..9f7c4b7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,6 +44,11 @@ Future main(List rawArgs) async { if (PlatformInfo.isDesktop) { await windowManager.ensureInitialized(); await windowManager.setPreventClose(true); + if (PlatformInfo.isMacOS) { + await windowManager.setTitleBarStyle(TitleBarStyle.hidden); + } else { + await windowManager.setTitleBarStyle(TitleBarStyle.normal); + } } if (PlatformInfo.isWindows) { await SMTCWindows.initialize(); diff --git a/lib/screens/about.dart b/lib/screens/about.dart index a90b41c..f1a44f6 100644 --- a/lib/screens/about.dart +++ b/lib/screens/about.dart @@ -11,9 +11,12 @@ class AboutScreen extends StatelessWidget { const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4)); - return Material( - color: Theme.of(context).colorScheme.surface, - child: SizedBox( + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: AppBar( + title: const Text('About'), + ), + body: SizedBox( width: double.infinity, child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/screens/player/mini.dart b/lib/screens/player/mini.dart index d9167d4..e7bcf1e 100644 --- a/lib/screens/player/mini.dart +++ b/lib/screens/player/mini.dart @@ -132,12 +132,13 @@ class _MiniPlayerScreenState extends State { await windowManager.setAlwaysOnTop( snapshot.data == true ? false : true, ); + setState(() {}); }, ); }, ), ], - ).paddingSymmetric(horizontal: 24), + ).paddingSymmetric(horizontal: 14), ), ), ), diff --git a/lib/shells/system_shell.dart b/lib/shells/system_shell.dart index f58fcd4..6f3ca9f 100644 --- a/lib/shells/system_shell.dart +++ b/lib/shells/system_shell.dart @@ -9,7 +9,7 @@ class SystemShell extends StatelessWidget { @override Widget build(BuildContext context) { - if (PlatformInfo.isDesktop) { + if (PlatformInfo.isMacOS) { return DragToMoveArea( child: Column( children: [ diff --git a/lib/widgets/player/bottom_player.dart b/lib/widgets/player/bottom_player.dart index 00a57c5..cd01c49 100644 --- a/lib/widgets/player/bottom_player.dart +++ b/lib/widgets/player/bottom_player.dart @@ -10,6 +10,7 @@ import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/services/audio_services/image.dart'; import 'package:rhythm_box/widgets/auto_cache_image.dart'; import 'package:rhythm_box/widgets/player/controls.dart'; +import 'package:rhythm_box/widgets/player/devices.dart'; import 'package:rhythm_box/widgets/player/track_details.dart'; import 'package:rhythm_box/widgets/tracks/querying_track_info.dart'; import 'package:rhythm_box/widgets/volume_slider.dart'; @@ -163,6 +164,16 @@ class _BottomPlayerState extends State child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + IconButton( + icon: const Icon(Icons.speaker, size: 18), + onPressed: () { + showModalBottomSheet( + useRootNavigator: true, + context: context, + builder: (context) => const PlayerDevicePopup(), + ); + }, + ), if (!widget.isMiniPlayer && PlatformInfo.isDesktop) IconButton( icon: const Icon( diff --git a/lib/widgets/player/devices.dart b/lib/widgets/player/devices.dart new file mode 100644 index 0000000..d630aa1 --- /dev/null +++ b/lib/widgets/player/devices.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:rhythm_box/services/audio_player/audio_player.dart'; + +class PlayerDevicePopup extends StatefulWidget { + const PlayerDevicePopup({super.key}); + + @override + State createState() => _PlayerDevicePopupState(); +} + +class _PlayerDevicePopupState extends State { + late Future> devicesFuture; + late Stream> devicesStream; + late Future selectedDeviceFuture; + late Stream selectedDeviceStream; + + @override + void initState() { + super.initState(); + devicesFuture = audioPlayer.devices; + devicesStream = audioPlayer.devicesStream; + selectedDeviceFuture = audioPlayer.selectedDevice; + selectedDeviceStream = audioPlayer.selectedDeviceStream; + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Devices', + style: Theme.of(context).textTheme.headlineSmall, + ).paddingOnly(left: 24, right: 24, top: 32, bottom: 16), + Expanded( + child: StreamBuilder>( + stream: devicesStream, + builder: (context, devicesSnapshot) { + return FutureBuilder>( + future: devicesFuture, + builder: (context, devicesFutureSnapshot) { + final devices = + devicesSnapshot.data ?? devicesFutureSnapshot.data; + + return StreamBuilder( + stream: selectedDeviceStream, + builder: (context, selectedDeviceSnapshot) { + return FutureBuilder( + future: selectedDeviceFuture, + builder: (context, selectedDeviceFutureSnapshot) { + final selectedDevice = selectedDeviceSnapshot.data ?? + selectedDeviceFutureSnapshot.data; + + if (devices == null || selectedDevice == null) { + return const CircularProgressIndicator(); + } + + return ListView.builder( + itemCount: devices.length, + itemBuilder: (context, idx) { + final device = devices[idx]; + return ListTile( + leading: const Icon(Icons.speaker), + title: Text(device.description), + subtitle: Text(device.name), + selected: selectedDevice == device, + onTap: () => audioPlayer.setAudioDevice(device), + ); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/volume_slider.dart b/lib/widgets/volume_slider.dart index 7236c3a..a3d84cf 100644 --- a/lib/widgets/volume_slider.dart +++ b/lib/widgets/volume_slider.dart @@ -67,7 +67,7 @@ class VolumeSlider extends StatelessWidget { } }, ), - slider, + SizedBox(width: 100, child: slider), ], ); }); diff --git a/pubspec.lock b/pubspec.lock index 77c64c9..e7c5bd0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1228,10 +1228,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" skeletonizer: dependency: "direct main" description: @@ -1522,10 +1522,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: @@ -1542,14 +1542,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: dependency: transitive description: