🐛 Fix the issue of the status bar not appearing on the playback page
This commit is contained in:
@@ -19,6 +19,7 @@ import 'package:groovybox/providers/audio_provider.dart';
|
|||||||
import 'package:groovybox/providers/db_provider.dart';
|
import 'package:groovybox/providers/db_provider.dart';
|
||||||
import 'package:groovybox/providers/lrc_fetcher_provider.dart';
|
import 'package:groovybox/providers/lrc_fetcher_provider.dart';
|
||||||
import 'package:groovybox/providers/settings_provider.dart';
|
import 'package:groovybox/providers/settings_provider.dart';
|
||||||
|
import 'package:groovybox/router.dart';
|
||||||
import 'package:groovybox/ui/widgets/mini_player.dart';
|
import 'package:groovybox/ui/widgets/mini_player.dart';
|
||||||
import 'package:groovybox/ui/widgets/track_tile.dart';
|
import 'package:groovybox/ui/widgets/track_tile.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -27,6 +28,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
enum ViewMode { cover, lyrics, queue }
|
enum ViewMode { cover, lyrics, queue }
|
||||||
|
|
||||||
@@ -60,6 +62,34 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
}, [defaultPlayerScreen]);
|
}, [defaultPlayerScreen]);
|
||||||
final isMobile = MediaQuery.sizeOf(context).width <= 800;
|
final isMobile = MediaQuery.sizeOf(context).width <= 800;
|
||||||
|
|
||||||
|
// Window maximized state
|
||||||
|
final isMaximized = useState(false);
|
||||||
|
|
||||||
|
// Update window maximized state
|
||||||
|
useEffect(() {
|
||||||
|
if (isDesktopPlatform()) {
|
||||||
|
windowManager.isMaximized().then((value) {
|
||||||
|
isMaximized.value = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Set system UI overlay style to ensure status bar is visible
|
||||||
|
useEffect(() {
|
||||||
|
// Remove edgeToEdge mode to show system title bar like main screen
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarIconBrightness: Brightness.light,
|
||||||
|
));
|
||||||
|
return () {
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return StreamBuilder<Playlist>(
|
return StreamBuilder<Playlist>(
|
||||||
stream: player.stream.playlist,
|
stream: player.stream.playlist,
|
||||||
initialData: player.state.playlist,
|
initialData: player.state.playlist,
|
||||||
@@ -142,7 +172,155 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: isDesktopPlatform() ? Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
// Main content
|
||||||
|
Positioned.fill(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
body: ClipRect(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
...background != null ? [background] : [],
|
||||||
|
// Main content (StreamBuilder)
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: devicePadding.top + 32, // Account for custom title bar
|
||||||
|
),
|
||||||
|
child: isMobile ? _MobileLayout(
|
||||||
|
player: player,
|
||||||
|
viewMode: viewMode,
|
||||||
|
media: media,
|
||||||
|
trackPath: media.uri,
|
||||||
|
) : _DesktopLayout(
|
||||||
|
player: player,
|
||||||
|
viewMode: viewMode,
|
||||||
|
media: media,
|
||||||
|
trackPath: media.uri,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// Back button
|
||||||
|
Positioned(
|
||||||
|
top: devicePadding.top + 48, // Adjusted for custom title bar
|
||||||
|
left: 16,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Symbols.keyboard_arrow_down),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
iconSize: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// view control button
|
||||||
|
if (!isDesktopPlatform())
|
||||||
|
_ViewToggleButton(viewMode: viewMode),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Custom title bar
|
||||||
|
Positioned(
|
||||||
|
top: devicePadding.top,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onPanStart: (details) {
|
||||||
|
windowManager.startDragging();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 32,
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
Theme.of(context).brightness == Brightness.dark
|
||||||
|
? 'assets/images/icon-dark.png'
|
||||||
|
: 'assets/images/icon.jpg',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'GroovyBox',
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 12, vertical: 5),
|
||||||
|
),
|
||||||
|
// Page actions
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.settings),
|
||||||
|
onPressed: () {
|
||||||
|
ref.read(routerProvider).push(AppRoutes.settings);
|
||||||
|
},
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
// Window controls
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.minimize),
|
||||||
|
onPressed: () => windowManager.minimize(),
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
isMaximized.value
|
||||||
|
? Symbols.fullscreen_exit
|
||||||
|
: Symbols.fullscreen,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
if (await windowManager.isMaximized()) {
|
||||||
|
windowManager.restore();
|
||||||
|
isMaximized.value = false;
|
||||||
|
} else {
|
||||||
|
windowManager.maximize();
|
||||||
|
isMaximized.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: () => windowManager.close(),
|
||||||
|
iconSize: 16,
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
constraints: BoxConstraints(),
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) : Scaffold(
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
),
|
||||||
body: ClipRect(
|
body: ClipRect(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -150,35 +328,22 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
// Main content (StreamBuilder)
|
// Main content (StreamBuilder)
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (isMobile) {
|
return Padding(
|
||||||
return Padding(
|
padding: EdgeInsets.only(
|
||||||
padding: EdgeInsets.only(
|
top: devicePadding.top + 40,
|
||||||
top:
|
),
|
||||||
devicePadding.top +
|
child: _MobileLayout(
|
||||||
40 +
|
|
||||||
(isDesktopPlatform() ? 28 : 0),
|
|
||||||
),
|
|
||||||
child: _MobileLayout(
|
|
||||||
player: player,
|
|
||||||
viewMode: viewMode,
|
|
||||||
media: media,
|
|
||||||
trackPath: media.uri,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return _DesktopLayout(
|
|
||||||
player: player,
|
player: player,
|
||||||
viewMode: viewMode,
|
viewMode: viewMode,
|
||||||
media: media,
|
media: media,
|
||||||
trackPath: media.uri,
|
trackPath: media.uri,
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// IconButton
|
// Back button for mobile platforms
|
||||||
Positioned(
|
Positioned(
|
||||||
top:
|
top: devicePadding.top + 16,
|
||||||
devicePadding.top + 16 + (isDesktopPlatform() ? 28 : 0),
|
|
||||||
left: 16,
|
left: 16,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Symbols.keyboard_arrow_down),
|
icon: const Icon(Symbols.keyboard_arrow_down),
|
||||||
@@ -187,6 +352,7 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
iconSize: 24,
|
iconSize: 24,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// view control button
|
||||||
_ViewToggleButton(viewMode: viewMode),
|
_ViewToggleButton(viewMode: viewMode),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -250,7 +416,6 @@ class _PlayerCoverControlsPanel extends StatelessWidget {
|
|||||||
required this.media,
|
required this.media,
|
||||||
required this.trackPath,
|
required this.trackPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
@@ -450,7 +615,6 @@ class _LyricsView extends StatelessWidget {
|
|||||||
|
|
||||||
class _PlayerCoverArt extends StatelessWidget {
|
class _PlayerCoverArt extends StatelessWidget {
|
||||||
final TrackMetadata? currentMetadata;
|
final TrackMetadata? currentMetadata;
|
||||||
|
|
||||||
const _PlayerCoverArt({required this.currentMetadata});
|
const _PlayerCoverArt({required this.currentMetadata});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -2258,4 +2422,4 @@ String _formatTimestamp(int milliseconds) {
|
|||||||
final millisecondsPart =
|
final millisecondsPart =
|
||||||
(duration.inMilliseconds % 1000) ~/ 10; // Show centiseconds
|
(duration.inMilliseconds % 1000) ~/ 10; // Show centiseconds
|
||||||
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}.${millisecondsPart.toString().padLeft(2, '0')}';
|
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}.${millisecondsPart.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user