mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: remote playback page to shadcn
This commit is contained in:
parent
780f5dee2e
commit
af295be8c6
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/components/button/back_button.dart';
|
import 'package:spotube/components/button/back_button.dart';
|
||||||
@ -60,6 +61,7 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final hasLeadingOrCanPop = leading.isNotEmpty || Navigator.canPop(context);
|
final hasLeadingOrCanPop = leading.isNotEmpty || Navigator.canPop(context);
|
||||||
|
final lastClicked = useRef<int>(DateTime.now().millisecondsSinceEpoch);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: height ?? 56,
|
height: height ?? 56,
|
||||||
@ -71,6 +73,23 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onHorizontalDragStart: (_) => onDrag(ref),
|
onHorizontalDragStart: (_) => onDrag(ref),
|
||||||
onVerticalDragStart: (_) => onDrag(ref),
|
onVerticalDragStart: (_) => onDrag(ref),
|
||||||
|
onTapDown: (details) async {
|
||||||
|
final systemTitlebar = ref.read(
|
||||||
|
userPreferencesProvider.select((s) => s.systemTitleBar));
|
||||||
|
if (!kIsDesktop || systemTitlebar) return;
|
||||||
|
|
||||||
|
int currMills = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
if ((currMills - lastClicked.value) < 500) {
|
||||||
|
if (await windowManager.isMaximized()) {
|
||||||
|
await windowManager.unmaximize();
|
||||||
|
} else {
|
||||||
|
await windowManager.maximize();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastClicked.value = currMills;
|
||||||
|
}
|
||||||
|
},
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
leading: leading.isEmpty &&
|
leading: leading.isEmpty &&
|
||||||
automaticallyImplyLeading &&
|
automaticallyImplyLeading &&
|
||||||
|
95
lib/components/ui/button_tile.dart
Normal file
95
lib/components/ui/button_tile.dart
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
|
class ButtonTile extends StatelessWidget {
|
||||||
|
final Widget? title;
|
||||||
|
final Widget? subtitle;
|
||||||
|
final Widget? leading;
|
||||||
|
final Widget? trailing;
|
||||||
|
final bool enabled;
|
||||||
|
final void Function()? onPressed;
|
||||||
|
final bool selected;
|
||||||
|
final ButtonVariance style;
|
||||||
|
|
||||||
|
const ButtonTile({
|
||||||
|
super.key,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.leading,
|
||||||
|
this.trailing,
|
||||||
|
this.enabled = true,
|
||||||
|
this.onPressed,
|
||||||
|
this.selected = false,
|
||||||
|
this.style = ButtonVariance.outline,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData(:colorScheme, :typography) = Theme.of(context);
|
||||||
|
|
||||||
|
return Button(
|
||||||
|
enabled: enabled,
|
||||||
|
onPressed: onPressed,
|
||||||
|
style: style.copyWith(
|
||||||
|
decoration: (context, states, value) {
|
||||||
|
final decoration = ButtonVariance.outline.decoration(context, states)
|
||||||
|
as BoxDecoration;
|
||||||
|
|
||||||
|
if (selected && style == ButtonVariance.outline) {
|
||||||
|
return decoration.copyWith(
|
||||||
|
border: Border.all(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
color: colorScheme.primary.withAlpha(25),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoration;
|
||||||
|
},
|
||||||
|
iconTheme: (context, states, value) {
|
||||||
|
final iconTheme = ButtonVariance.outline.iconTheme(context, states);
|
||||||
|
|
||||||
|
if (selected && style == ButtonVariance.outline) {
|
||||||
|
return iconTheme.copyWith(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconTheme;
|
||||||
|
},
|
||||||
|
textStyle: (context, states, value) {
|
||||||
|
final textStyle = ButtonVariance.outline.textStyle(context, states);
|
||||||
|
|
||||||
|
if (selected && style == ButtonVariance.outline) {
|
||||||
|
return textStyle.copyWith(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textStyle;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Basic(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
leadingAlignment: Alignment.center,
|
||||||
|
trailingAlignment: Alignment.center,
|
||||||
|
leading: leading,
|
||||||
|
title: title,
|
||||||
|
subtitle:
|
||||||
|
style == ButtonVariance.outline && selected && subtitle != null
|
||||||
|
? DefaultTextStyle(
|
||||||
|
style: typography.xSmall.copyWith(
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
child: subtitle!,
|
||||||
|
)
|
||||||
|
: subtitle,
|
||||||
|
trailing: trailing,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,7 @@
|
|||||||
"pause_playback": "Pause Playback",
|
"pause_playback": "Pause Playback",
|
||||||
"resume_playback": "Resume Playback",
|
"resume_playback": "Resume Playback",
|
||||||
"loop_track": "Loop track",
|
"loop_track": "Loop track",
|
||||||
|
"no_loop": "No loop",
|
||||||
"repeat_playlist": "Repeat playlist",
|
"repeat_playlist": "Repeat playlist",
|
||||||
"queue": "Queue",
|
"queue": "Queue",
|
||||||
"alternative_track_sources": "Alternative track sources",
|
"alternative_track_sources": "Alternative track sources",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/ui/button_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ class ConnectPageLocalDevices extends HookWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ThemeData(:textTheme) = Theme.of(context);
|
final ThemeData(:typography) = Theme.of(context);
|
||||||
final devicesFuture = useFuture(audioPlayer.devices);
|
final devicesFuture = useFuture(audioPlayer.devices);
|
||||||
final devicesStream = useStream(audioPlayer.devicesStream);
|
final devicesStream = useStream(audioPlayer.devicesStream);
|
||||||
final selectedDeviceFuture = useFuture(audioPlayer.selectedDevice);
|
final selectedDeviceFuture = useFuture(audioPlayer.selectedDevice);
|
||||||
@ -32,7 +32,7 @@ class ConnectPageLocalDevices extends HookWidget {
|
|||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.this_device,
|
context.l10n.this_device,
|
||||||
style: textTheme.titleMedium,
|
style: typography.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -43,14 +43,12 @@ class ConnectPageLocalDevices extends HookWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final device = devices[index];
|
final device = devices[index];
|
||||||
|
|
||||||
return Card(
|
return ButtonTile(
|
||||||
child: ListTile(
|
selected: selectedDevice == device,
|
||||||
|
onPressed: () => audioPlayer.setAudioDevice(device),
|
||||||
leading: const Icon(SpotubeIcons.speaker),
|
leading: const Icon(SpotubeIcons.speaker),
|
||||||
title: Text(device.description),
|
title: Text(device.description),
|
||||||
subtitle: Text(device.name),
|
subtitle: Text(device.name),
|
||||||
selected: selectedDevice == device,
|
|
||||||
onTap: () => audioPlayer.setAudioDevice(device),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -224,7 +224,7 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SliverGap(100),
|
const SliverSafeArea(sliver: SliverGap(100)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/ui/button_tile.dart';
|
||||||
import 'package:spotube/modules/connect/local_devices.dart';
|
import 'package:spotube/modules/connect/local_devices.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -16,22 +16,19 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:colorScheme, :textTheme) = Theme.of(context);
|
final ThemeData(:colorScheme, :typography) = Theme.of(context);
|
||||||
|
|
||||||
final connectClients = ref.watch(connectClientsProvider);
|
final connectClients = ref.watch(connectClientsProvider);
|
||||||
final connectClientsNotifier = ref.read(connectClientsProvider.notifier);
|
final connectClientsNotifier = ref.read(connectClientsProvider.notifier);
|
||||||
final discoveredDevices = connectClients.asData?.value.services;
|
final discoveredDevices = connectClients.asData?.value.services;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: TitleBar(
|
headers: [
|
||||||
|
TitleBar(
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
title: Text(context.l10n.devices),
|
title: Text(context.l10n.devices),
|
||||||
),
|
)
|
||||||
body: ListTileTheme(
|
],
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
selectedTileColor: colorScheme.secondary.withOpacity(0.1),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
@ -41,7 +38,7 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.remote,
|
context.l10n.remote,
|
||||||
style: textTheme.titleMedium,
|
style: typography.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -54,8 +51,8 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
final selected =
|
final selected =
|
||||||
connectClients.asData?.value.resolvedService?.name ==
|
connectClients.asData?.value.resolvedService?.name ==
|
||||||
device.name;
|
device.name;
|
||||||
return Card(
|
return ButtonTile(
|
||||||
child: ListTile(
|
selected: selected,
|
||||||
leading: const Icon(SpotubeIcons.monitor),
|
leading: const Icon(SpotubeIcons.monitor),
|
||||||
title: Text(device.name),
|
title: Text(device.name),
|
||||||
subtitle: selected
|
subtitle: selected
|
||||||
@ -64,8 +61,15 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
":${connectClients.asData?.value.resolvedService?.port}",
|
":${connectClients.asData?.value.resolvedService?.port}",
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
selected: selected,
|
trailing: selected
|
||||||
onTap: () {
|
? IconButton.outline(
|
||||||
|
icon: const Icon(SpotubeIcons.power),
|
||||||
|
size: ButtonSize.small,
|
||||||
|
onPressed: () =>
|
||||||
|
connectClientsNotifier.clearResolvedService(),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
onPressed: () {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
ServiceUtils.pushNamed(
|
ServiceUtils.pushNamed(
|
||||||
context,
|
context,
|
||||||
@ -75,14 +79,6 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
connectClientsNotifier.resolveService(device);
|
connectClientsNotifier.resolveService(device);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailing: selected
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(SpotubeIcons.power),
|
|
||||||
onPressed: () =>
|
|
||||||
connectClientsNotifier.clearResolvedService(),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -90,7 +86,6 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/modules/player/player_queue.dart';
|
import 'package:spotube/modules/player/player_queue.dart';
|
||||||
import 'package:spotube/modules/player/volume_slider.dart';
|
import 'package:spotube/modules/player/volume_slider.dart';
|
||||||
@ -53,7 +53,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:textTheme, :colorScheme) = Theme.of(context);
|
final ThemeData(:typography, :colorScheme) = Theme.of(context);
|
||||||
|
|
||||||
final resolvedService =
|
final resolvedService =
|
||||||
ref.watch(connectClientsProvider).asData?.value.resolvedService;
|
ref.watch(connectClientsProvider).asData?.value.resolvedService;
|
||||||
@ -63,23 +63,6 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
final shuffled = ref.watch(shuffleProvider);
|
final shuffled = ref.watch(shuffleProvider);
|
||||||
final loopMode = ref.watch(loopModeProvider);
|
final loopMode = ref.watch(loopModeProvider);
|
||||||
|
|
||||||
final resumePauseStyle = IconButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.primary,
|
|
||||||
foregroundColor: colorScheme.onPrimary,
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
iconSize: 24,
|
|
||||||
);
|
|
||||||
final buttonStyle = IconButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.surface.withOpacity(0.4),
|
|
||||||
minimumSize: const Size(28, 28),
|
|
||||||
);
|
|
||||||
|
|
||||||
final activeButtonStyle = IconButton.styleFrom(
|
|
||||||
backgroundColor: colorScheme.primaryContainer,
|
|
||||||
foregroundColor: colorScheme.onPrimaryContainer,
|
|
||||||
minimumSize: const Size(28, 28),
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.listen(connectClientsProvider, (prev, next) {
|
ref.listen(connectClientsProvider, (prev, next) {
|
||||||
if (next.asData?.value.resolvedService == null) {
|
if (next.asData?.value.resolvedService == null) {
|
||||||
context.pop();
|
context.pop();
|
||||||
@ -87,12 +70,15 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: TitleBar(
|
headers: [
|
||||||
|
TitleBar(
|
||||||
title: Text(resolvedService!.name),
|
title: Text(resolvedService!.name),
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
),
|
)
|
||||||
body: LayoutBuilder(builder: (context, constrains) {
|
],
|
||||||
|
child: LayoutBuilder(builder: (context, constrains) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -106,7 +92,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
vertical: 10,
|
vertical: 10,
|
||||||
).copyWith(top: 0),
|
).copyWith(top: 0),
|
||||||
constraints:
|
constraints:
|
||||||
const BoxConstraints(maxHeight: 400, maxWidth: 400),
|
const BoxConstraints(maxHeight: 350, maxWidth: 350),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
child: UniversalImage(
|
child: UniversalImage(
|
||||||
@ -126,7 +112,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: AnchorButton(
|
child: AnchorButton(
|
||||||
playlist.activeTrack?.name ?? "",
|
playlist.activeTrack?.name ?? "",
|
||||||
style: textTheme.titleLarge!,
|
style: typography.h4,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (playlist.activeTrack == null) return;
|
if (playlist.activeTrack == null) return;
|
||||||
ServiceUtils.pushNamed(
|
ServiceUtils.pushNamed(
|
||||||
@ -142,7 +128,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: ArtistLink(
|
child: ArtistLink(
|
||||||
artists: playlist.activeTrack?.artists ?? [],
|
artists: playlist.activeTrack?.artists ?? [],
|
||||||
textStyle: textTheme.bodyMedium!,
|
textStyle: typography.normal,
|
||||||
mainAxisAlignment: WrapAlignment.start,
|
mainAxisAlignment: WrapAlignment.start,
|
||||||
onOverflowArtistClick: () =>
|
onOverflowArtistClick: () =>
|
||||||
ServiceUtils.pushNamed(
|
ServiceUtils.pushNamed(
|
||||||
@ -164,19 +150,25 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
final position = ref.watch(positionProvider);
|
final position = ref.watch(positionProvider);
|
||||||
final duration = ref.watch(durationProvider);
|
final duration = ref.watch(durationProvider);
|
||||||
|
|
||||||
|
final progress = duration.inSeconds == 0
|
||||||
|
? 0
|
||||||
|
: position.inSeconds / duration.inSeconds;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Slider(
|
Slider(
|
||||||
value: position > duration
|
value:
|
||||||
? 0
|
SliderValue.single(progress.toDouble()),
|
||||||
: position.inSeconds.toDouble(),
|
|
||||||
min: 0,
|
|
||||||
max: duration.inSeconds.toDouble(),
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
connectNotifier
|
connectNotifier.seek(
|
||||||
.seek(Duration(seconds: value.toInt()));
|
Duration(
|
||||||
|
seconds:
|
||||||
|
(value.value * duration.inSeconds)
|
||||||
|
.toInt(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -197,43 +189,59 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Tooltip(
|
||||||
tooltip: shuffled
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(
|
||||||
|
shuffled
|
||||||
? context.l10n.unshuffle_playlist
|
? context.l10n.unshuffle_playlist
|
||||||
: context.l10n.shuffle_playlist,
|
: context.l10n.shuffle_playlist,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
icon: const Icon(SpotubeIcons.shuffle),
|
icon: const Icon(SpotubeIcons.shuffle),
|
||||||
style: shuffled ? activeButtonStyle : buttonStyle,
|
variance: shuffled
|
||||||
|
? ButtonVariance.secondary
|
||||||
|
: ButtonVariance.ghost,
|
||||||
onPressed: playlist.activeTrack == null
|
onPressed: playlist.activeTrack == null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
connectNotifier.setShuffle(!shuffled);
|
connectNotifier.setShuffle(!shuffled);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
tooltip: context.l10n.previous_track,
|
Tooltip(
|
||||||
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(context.l10n.previous_track),
|
||||||
|
),
|
||||||
|
child: IconButton.ghost(
|
||||||
icon: const Icon(SpotubeIcons.skipBack),
|
icon: const Icon(SpotubeIcons.skipBack),
|
||||||
onPressed: playlist.activeTrack == null
|
onPressed: playlist.activeTrack == null
|
||||||
? null
|
? null
|
||||||
: connectNotifier.previous,
|
: connectNotifier.previous,
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
tooltip: playing
|
Tooltip(
|
||||||
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(
|
||||||
|
playing
|
||||||
? context.l10n.pause_playback
|
? context.l10n.pause_playback
|
||||||
: context.l10n.resume_playback,
|
: context.l10n.resume_playback,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton.primary(
|
||||||
|
shape: ButtonShape.circle,
|
||||||
icon: playlist.activeTrack == null
|
icon: playlist.activeTrack == null
|
||||||
? SizedBox(
|
? const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
color: colorScheme.onPrimary,
|
onSurface: false),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: Icon(
|
: Icon(
|
||||||
playing
|
playing
|
||||||
? SpotubeIcons.pause
|
? SpotubeIcons.pause
|
||||||
: SpotubeIcons.play,
|
: SpotubeIcons.play,
|
||||||
),
|
),
|
||||||
style: resumePauseStyle,
|
|
||||||
onPressed: playlist.activeTrack == null
|
onPressed: playlist.activeTrack == null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
@ -244,28 +252,37 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
tooltip: context.l10n.next_track,
|
Tooltip(
|
||||||
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(context.l10n.next_track)),
|
||||||
|
child: IconButton.ghost(
|
||||||
icon: const Icon(SpotubeIcons.skipForward),
|
icon: const Icon(SpotubeIcons.skipForward),
|
||||||
onPressed: playlist.activeTrack == null
|
onPressed: playlist.activeTrack == null
|
||||||
? null
|
? null
|
||||||
: connectNotifier.next,
|
: connectNotifier.next,
|
||||||
),
|
),
|
||||||
IconButton(
|
),
|
||||||
tooltip: loopMode == PlaylistMode.single
|
Tooltip(
|
||||||
|
tooltip: TooltipContainer(
|
||||||
|
child: Text(
|
||||||
|
loopMode == PlaylistMode.single
|
||||||
? context.l10n.loop_track
|
? context.l10n.loop_track
|
||||||
: loopMode == PlaylistMode.loop
|
: loopMode == PlaylistMode.loop
|
||||||
? context.l10n.repeat_playlist
|
? context.l10n.repeat_playlist
|
||||||
: null,
|
: context.l10n.no_loop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
loopMode == PlaylistMode.single
|
loopMode == PlaylistMode.single
|
||||||
? SpotubeIcons.repeatOne
|
? SpotubeIcons.repeatOne
|
||||||
: SpotubeIcons.repeat,
|
: SpotubeIcons.repeat,
|
||||||
),
|
),
|
||||||
style: loopMode == PlaylistMode.single ||
|
variance: loopMode == PlaylistMode.single ||
|
||||||
loopMode == PlaylistMode.loop
|
loopMode == PlaylistMode.loop
|
||||||
? activeButtonStyle
|
? ButtonVariance.secondary
|
||||||
: buttonStyle,
|
: ButtonVariance.ghost,
|
||||||
onPressed: playlist.activeTrack == null
|
onPressed: playlist.activeTrack == null
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
@ -275,15 +292,52 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
PlaylistMode.single,
|
PlaylistMode.single,
|
||||||
PlaylistMode.single =>
|
PlaylistMode.single =>
|
||||||
PlaylistMode.none,
|
PlaylistMode.none,
|
||||||
PlaylistMode.none => PlaylistMode.loop,
|
PlaylistMode.none =>
|
||||||
|
PlaylistMode.loop,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(30),
|
const SliverGap(30),
|
||||||
|
if (constrains.mdAndDown)
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: Button.outline(
|
||||||
|
leading: const Icon(SpotubeIcons.queue),
|
||||||
|
child: Text(context.l10n.queue),
|
||||||
|
onPressed: () {
|
||||||
|
openDrawer(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
draggable: true,
|
||||||
|
barrierColor: Colors.black.withAlpha(100),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
transformBackdrop: false,
|
||||||
|
position: OverlayPosition.bottom,
|
||||||
|
surfaceBlur: context.theme.surfaceBlur,
|
||||||
|
surfaceOpacity: 0.7,
|
||||||
|
expands: true,
|
||||||
|
builder: (context) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight:
|
||||||
|
MediaQuery.sizeOf(context).height *
|
||||||
|
0.8,
|
||||||
|
),
|
||||||
|
child: const RemotePlayerQueue(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverGap(30),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
@ -300,25 +354,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverGap(30),
|
const SliverSafeArea(sliver: SliverGap(10)),
|
||||||
if (constrains.mdAndDown)
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
||||||
sliver: SliverToBoxAdapter(
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
icon: const Icon(SpotubeIcons.queue),
|
|
||||||
label: Text(context.l10n.queue),
|
|
||||||
onPressed: () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return const RemotePlayerQueue();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ar": [
|
"ar": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -9,6 +10,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -18,6 +20,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ca": [
|
"ca": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -27,6 +30,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"cs": [
|
"cs": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -36,6 +40,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -45,6 +50,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -54,6 +60,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"eu": [
|
"eu": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -63,6 +70,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -72,6 +80,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -81,6 +90,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -90,6 +100,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -99,6 +110,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -108,6 +120,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -117,6 +130,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -126,6 +140,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ka": [
|
"ka": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -135,6 +150,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -144,6 +160,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ne": [
|
"ne": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -153,6 +170,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -162,6 +180,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -171,6 +190,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -180,6 +200,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -189,6 +210,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"th": [
|
"th": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -198,6 +220,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -207,6 +230,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -216,6 +240,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
@ -225,6 +250,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
"no_loop",
|
||||||
"undo",
|
"undo",
|
||||||
"download_all",
|
"download_all",
|
||||||
"add_all_to_playlist",
|
"add_all_to_playlist",
|
||||||
|
Loading…
Reference in New Issue
Block a user