✨ Keyboard support on player
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:drift/drift.dart' as drift;
|
import 'package:drift/drift.dart' as drift;
|
||||||
import 'package:groovybox/data/db.dart' as db;
|
import 'package:groovybox/data/db.dart' as db;
|
||||||
import 'package:groovybox/logic/lyrics_parser.dart';
|
import 'package:groovybox/logic/lyrics_parser.dart';
|
||||||
@@ -37,70 +38,94 @@ class PlayerScreen extends HookConsumerWidget {
|
|||||||
final path = Uri.decodeFull(Uri.parse(media.uri).path);
|
final path = Uri.decodeFull(Uri.parse(media.uri).path);
|
||||||
final metadataAsync = ref.watch(trackMetadataProvider(path));
|
final metadataAsync = ref.watch(trackMetadataProvider(path));
|
||||||
|
|
||||||
return Scaffold(
|
return Focus(
|
||||||
body: Stack(
|
autofocus: true,
|
||||||
children: [
|
onKeyEvent: (node, event) {
|
||||||
// Main content (StreamBuilder)
|
if (event is KeyDownEvent) {
|
||||||
Builder(
|
if (event.logicalKey == LogicalKeyboardKey.space) {
|
||||||
builder: (context) {
|
if (player.state.playing) {
|
||||||
if (isMobile) {
|
player.pause();
|
||||||
return Padding(
|
} else {
|
||||||
padding: EdgeInsets.only(
|
player.play();
|
||||||
top: MediaQuery.of(context).padding.top + 64,
|
}
|
||||||
),
|
return KeyEventResult.handled;
|
||||||
child: _MobileLayout(
|
} else if (event.logicalKey == LogicalKeyboardKey.bracketLeft) {
|
||||||
|
player.previous();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else if (event.logicalKey == LogicalKeyboardKey.bracketRight) {
|
||||||
|
player.next();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
// Main content (StreamBuilder)
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (isMobile) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: MediaQuery.of(context).padding.top + 64,
|
||||||
|
),
|
||||||
|
child: _MobileLayout(
|
||||||
|
player: player,
|
||||||
|
tabController: tabController,
|
||||||
|
metadataAsync: metadataAsync,
|
||||||
|
media: media,
|
||||||
|
trackPath: path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return _DesktopLayout(
|
||||||
player: player,
|
player: player,
|
||||||
tabController: tabController,
|
|
||||||
metadataAsync: metadataAsync,
|
metadataAsync: metadataAsync,
|
||||||
media: media,
|
media: media,
|
||||||
trackPath: path,
|
trackPath: path,
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
} else {
|
},
|
||||||
return _DesktopLayout(
|
|
||||||
player: player,
|
|
||||||
metadataAsync: metadataAsync,
|
|
||||||
media: media,
|
|
||||||
trackPath: path,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// IconButton
|
|
||||||
Positioned(
|
|
||||||
top: MediaQuery.of(context).padding.top + 16,
|
|
||||||
left: 16,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.keyboard_arrow_down),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
iconSize: 24,
|
|
||||||
),
|
),
|
||||||
),
|
// IconButton
|
||||||
// TabBar (if mobile)
|
|
||||||
if (isMobile)
|
|
||||||
Positioned(
|
Positioned(
|
||||||
top: MediaQuery.of(context).padding.top + 14,
|
top: MediaQuery.of(context).padding.top + 16,
|
||||||
left: 54,
|
left: 16,
|
||||||
right: 54,
|
child: IconButton(
|
||||||
child: Padding(
|
icon: const Icon(Icons.keyboard_arrow_down),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: TabBar(
|
padding: EdgeInsets.zero,
|
||||||
controller: tabController,
|
iconSize: 24,
|
||||||
tabAlignment: TabAlignment.fill,
|
|
||||||
tabs: const [
|
|
||||||
Tab(text: 'Cover'),
|
|
||||||
Tab(text: 'Lyrics'),
|
|
||||||
],
|
|
||||||
dividerHeight: 0,
|
|
||||||
indicatorColor: Colors.transparent,
|
|
||||||
overlayColor: WidgetStatePropertyAll(Colors.transparent),
|
|
||||||
splashFactory: NoSplash.splashFactory,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_LyricsRefreshButton(trackPath: path),
|
// TabBar (if mobile)
|
||||||
],
|
if (isMobile)
|
||||||
|
Positioned(
|
||||||
|
top: MediaQuery.of(context).padding.top + 14,
|
||||||
|
left: 54,
|
||||||
|
right: 54,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
tabAlignment: TabAlignment.fill,
|
||||||
|
tabs: const [
|
||||||
|
Tab(text: 'Cover'),
|
||||||
|
Tab(text: 'Lyrics'),
|
||||||
|
],
|
||||||
|
dividerHeight: 0,
|
||||||
|
indicatorColor: Colors.transparent,
|
||||||
|
overlayColor: WidgetStatePropertyAll(
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
splashFactory: NoSplash.splashFactory,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_LyricsRefreshButton(trackPath: path),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -251,29 +276,38 @@ class _PlayerCoverArt extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return metadataAsync.when(
|
return metadataAsync.when(
|
||||||
data: (meta) => Container(
|
data: (meta) => Center(
|
||||||
decoration: BoxDecoration(
|
child: AspectRatio(
|
||||||
color: Colors.grey[800],
|
aspectRatio: 1,
|
||||||
borderRadius: BorderRadius.circular(24),
|
child: Container(
|
||||||
boxShadow: [
|
decoration: BoxDecoration(
|
||||||
BoxShadow(
|
color: Colors.grey[800],
|
||||||
color: Colors.black.withValues(alpha: 0.3),
|
borderRadius: BorderRadius.circular(24),
|
||||||
blurRadius: 20,
|
boxShadow: [
|
||||||
offset: const Offset(0, 10),
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
image: meta.artBytes != null
|
||||||
|
? DecorationImage(
|
||||||
|
image: MemoryImage(meta.artBytes!),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
],
|
child: meta.artBytes == null
|
||||||
image: meta.artBytes != null
|
? const Center(
|
||||||
? DecorationImage(
|
child: Icon(
|
||||||
image: MemoryImage(meta.artBytes!),
|
Icons.music_note,
|
||||||
fit: BoxFit.cover,
|
size: 80,
|
||||||
)
|
color: Colors.white54,
|
||||||
: null,
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: meta.artBytes == null
|
|
||||||
? const Center(
|
|
||||||
child: Icon(Icons.music_note, size: 80, color: Colors.white54),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (_, _) => Container(
|
error: (_, _) => Container(
|
||||||
@@ -994,7 +1028,7 @@ class _PlayerControls extends HookWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
loading: () => const SizedBox(height: 24),
|
loading: () => const SizedBox(height: 24),
|
||||||
error: (_, __) => const SizedBox.shrink(),
|
error: (_, _) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user