mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat(lyrics): use official spotify API for fetching lyrics and add zoom controls
This commit is contained in:
parent
bd48ca44ee
commit
10d0660972
@ -65,4 +65,6 @@ abstract class SpotubeIcons {
|
|||||||
static const minimize = FeatherIcons.chevronDown;
|
static const minimize = FeatherIcons.chevronDown;
|
||||||
static const personalized = FeatherIcons.star;
|
static const personalized = FeatherIcons.star;
|
||||||
static const genres = FeatherIcons.music;
|
static const genres = FeatherIcons.music;
|
||||||
|
static const zoomIn = FeatherIcons.zoomIn;
|
||||||
|
static const zoomOut = FeatherIcons.zoomOut;
|
||||||
}
|
}
|
||||||
|
54
lib/components/lyrics/zoom_controls.dart
Normal file
54
lib/components/lyrics/zoom_controls.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
|
||||||
|
class ZoomControls extends HookWidget {
|
||||||
|
final int value;
|
||||||
|
final ValueChanged<int> onChanged;
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
|
||||||
|
const ZoomControls({
|
||||||
|
Key? key,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
this.min = 50,
|
||||||
|
this.max = 200,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: PlatformTheme.of(context)
|
||||||
|
.secondaryBackgroundColor
|
||||||
|
?.withOpacity(0.7),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(maxHeight: 50),
|
||||||
|
margin: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
PlatformIconButton(
|
||||||
|
icon: const Icon(SpotubeIcons.zoomOut),
|
||||||
|
onPressed: () {
|
||||||
|
if (value == min) return;
|
||||||
|
onChanged(value - 10);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PlatformText("$value%"),
|
||||||
|
PlatformIconButton(
|
||||||
|
icon: const Icon(SpotubeIcons.zoomIn),
|
||||||
|
onPressed: () {
|
||||||
|
if (value == max) return;
|
||||||
|
onChanged(value + 10);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,26 @@ class SubtitleSimple {
|
|||||||
required this.lyrics,
|
required this.lyrics,
|
||||||
required this.rating,
|
required this.rating,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory SubtitleSimple.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SubtitleSimple(
|
||||||
|
uri: Uri.parse(json["uri"] as String),
|
||||||
|
name: json["name"] as String,
|
||||||
|
lyrics: (json["lyrics"] as List<dynamic>)
|
||||||
|
.map((e) => LyricSlice.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
rating: json["rating"] as int,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"uri": uri.toString(),
|
||||||
|
"name": name,
|
||||||
|
"lyrics": lyrics.map((e) => e.toJson()).toList(),
|
||||||
|
"rating": rating,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LyricSlice {
|
class LyricSlice {
|
||||||
@ -17,6 +37,20 @@ class LyricSlice {
|
|||||||
|
|
||||||
LyricSlice({required this.time, required this.text});
|
LyricSlice({required this.time, required this.text});
|
||||||
|
|
||||||
|
factory LyricSlice.fromJson(Map<String, dynamic> json) {
|
||||||
|
return LyricSlice(
|
||||||
|
time: Duration(milliseconds: json["time"]),
|
||||||
|
text: json["text"] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"time": time.inMilliseconds,
|
||||||
|
"text": text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "LyricsSlice({time: $time, text: $text})";
|
return "LyricsSlice({time: $time, text: $text})";
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
|
||||||
|
|
||||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
|
||||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
|
||||||
|
|
||||||
class GeniusLyrics extends HookConsumerWidget {
|
|
||||||
final PaletteColor palette;
|
|
||||||
final bool? isModal;
|
|
||||||
const GeniusLyrics({
|
|
||||||
required this.palette,
|
|
||||||
this.isModal,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, ref) {
|
|
||||||
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
|
||||||
final geniusLyricsQuery = useQueries.lyrics.static(
|
|
||||||
playlist?.activeTrack,
|
|
||||||
ref.watch(userPreferencesProvider).geniusAccessToken,
|
|
||||||
);
|
|
||||||
final breakpoint = useBreakpoints();
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
if (isModal != true) ...[
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
playlist?.activeTrack.name ?? "",
|
|
||||||
style: breakpoint >= Breakpoints.md
|
|
||||||
? textTheme.displaySmall
|
|
||||||
: textTheme.headlineMedium?.copyWith(
|
|
||||||
fontSize: 25,
|
|
||||||
color: palette.titleTextColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
TypeConversionUtils.artists_X_String<Artist>(
|
|
||||||
playlist?.activeTrack.artists ?? []),
|
|
||||||
style: (breakpoint >= Breakpoints.md
|
|
||||||
? textTheme.headlineSmall
|
|
||||||
: textTheme.titleLarge)
|
|
||||||
?.copyWith(color: palette.bodyTextColor),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
if (geniusLyricsQuery.isLoading ||
|
|
||||||
geniusLyricsQuery.isRefreshing) {
|
|
||||||
return const ShimmerLyrics();
|
|
||||||
} else if (geniusLyricsQuery.hasError) {
|
|
||||||
return Text(
|
|
||||||
"Sorry, no Lyrics were found for `${playlist?.activeTrack.name}` :'(\n${geniusLyricsQuery.error.toString()}",
|
|
||||||
style: textTheme.bodyLarge?.copyWith(
|
|
||||||
color: palette.bodyTextColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final lyrics = geniusLyricsQuery.data;
|
|
||||||
|
|
||||||
return Text(
|
|
||||||
lyrics == null && playlist?.activeTrack == null
|
|
||||||
? "No Track being played currently"
|
|
||||||
: lyrics ?? "",
|
|
||||||
style:
|
|
||||||
TextStyle(color: palette.bodyTextColor, fontSize: 18),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Align(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text("Powered by genius.com"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,7 @@ import 'package:spotube/components/shared/page_window_title_bar.dart';
|
|||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/hooks/use_custom_status_bar_color.dart';
|
import 'package:spotube/hooks/use_custom_status_bar_color.dart';
|
||||||
import 'package:spotube/hooks/use_palette_color.dart';
|
import 'package:spotube/hooks/use_palette_color.dart';
|
||||||
import 'package:spotube/pages/lyrics/genius_lyrics.dart';
|
import 'package:spotube/pages/lyrics/plain_lyrics.dart';
|
||||||
import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
@ -41,7 +41,7 @@ class LyricsPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
Widget body = [
|
Widget body = [
|
||||||
SyncedLyrics(palette: palette, isModal: isModal),
|
SyncedLyrics(palette: palette, isModal: isModal),
|
||||||
GeniusLyrics(palette: palette, isModal: isModal),
|
PlainLyrics(palette: palette, isModal: isModal),
|
||||||
][index.value];
|
][index.value];
|
||||||
|
|
||||||
final tabbar = PreferredSize(
|
final tabbar = PreferredSize(
|
||||||
@ -58,7 +58,7 @@ class LyricsPage extends HookConsumerWidget {
|
|||||||
color: PlatformTextTheme.of(context).caption?.color,
|
color: PlatformTextTheme.of(context).caption?.color,
|
||||||
),
|
),
|
||||||
PlatformTab(
|
PlatformTab(
|
||||||
label: "Genius",
|
label: "Plain",
|
||||||
icon: const SizedBox.shrink(),
|
icon: const SizedBox.shrink(),
|
||||||
color: PlatformTextTheme.of(context).caption?.color,
|
color: PlatformTextTheme.of(context).caption?.color,
|
||||||
),
|
),
|
||||||
@ -71,7 +71,7 @@ class LyricsPage extends HookConsumerWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).backgroundColor.withOpacity(.4),
|
color: Theme.of(context).colorScheme.background.withOpacity(.4),
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(10),
|
topLeft: Radius.circular(10),
|
||||||
topRight: Radius.circular(10),
|
topRight: Radius.circular(10),
|
||||||
|
132
lib/pages/lyrics/plain_lyrics.dart
Normal file
132
lib/pages/lyrics/plain_lyrics.dart
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/lyrics/zoom_controls.dart';
|
||||||
|
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
||||||
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
|
|
||||||
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
|
class PlainLyrics extends HookConsumerWidget {
|
||||||
|
final PaletteColor palette;
|
||||||
|
final bool? isModal;
|
||||||
|
const PlainLyrics({
|
||||||
|
required this.palette,
|
||||||
|
this.isModal,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final playlist = ref.watch(PlaylistQueueNotifier.provider);
|
||||||
|
final lyricsQuery = useQueries.lyrics.spotifySynced(
|
||||||
|
ref,
|
||||||
|
playlist?.activeTrack,
|
||||||
|
);
|
||||||
|
final breakpoint = useBreakpoints();
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
final textZoomLevel = useState<int>(100);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (isModal != true) ...[
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
playlist?.activeTrack.name ?? "",
|
||||||
|
style: breakpoint >= Breakpoints.md
|
||||||
|
? textTheme.displaySmall
|
||||||
|
: textTheme.headlineMedium?.copyWith(
|
||||||
|
fontSize: 25,
|
||||||
|
color: palette.titleTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
TypeConversionUtils.artists_X_String<Artist>(
|
||||||
|
playlist?.activeTrack.artists ?? []),
|
||||||
|
style: (breakpoint >= Breakpoints.md
|
||||||
|
? textTheme.headlineSmall
|
||||||
|
: textTheme.titleLarge)
|
||||||
|
?.copyWith(color: palette.bodyTextColor),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (lyricsQuery.isLoading || lyricsQuery.isRefreshing) {
|
||||||
|
return const ShimmerLyrics();
|
||||||
|
} else if (lyricsQuery.hasError) {
|
||||||
|
return Text(
|
||||||
|
"Sorry, no Lyrics were found for `${playlist?.activeTrack.name}` :'(\n${lyricsQuery.error.toString()}",
|
||||||
|
style: textTheme.bodyLarge?.copyWith(
|
||||||
|
color: palette.bodyTextColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final lyrics =
|
||||||
|
lyricsQuery.data?.lyrics.mapIndexed((i, e) {
|
||||||
|
final next =
|
||||||
|
lyricsQuery.data?.lyrics.elementAtOrNull(i + 1);
|
||||||
|
if (next != null &&
|
||||||
|
e.time - next.time >
|
||||||
|
const Duration(milliseconds: 700)) {
|
||||||
|
return "${e.text}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.text;
|
||||||
|
}).join("\n");
|
||||||
|
|
||||||
|
return AnimatedDefaultTextStyle(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
style: TextStyle(
|
||||||
|
color: palette.bodyTextColor,
|
||||||
|
fontSize: 24 * textZoomLevel.value / 100,
|
||||||
|
height: textZoomLevel.value < 70
|
||||||
|
? 1.5
|
||||||
|
: textZoomLevel.value > 150
|
||||||
|
? 1.7
|
||||||
|
: 2,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
lyrics == null && playlist?.activeTrack == null
|
||||||
|
? "No Track being played currently"
|
||||||
|
: lyrics ?? "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: ZoomControls(
|
||||||
|
value: textZoomLevel.value,
|
||||||
|
onChanged: (value) => textZoomLevel.value = value,
|
||||||
|
min: 50,
|
||||||
|
max: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -6,14 +5,13 @@ import 'package:palette_generator/palette_generator.dart';
|
|||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/lyrics/zoom_controls.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart';
|
||||||
import 'package:spotube/components/shared/spotube_marquee_text.dart';
|
import 'package:spotube/components/shared/spotube_marquee_text.dart';
|
||||||
import 'package:spotube/components/lyrics/lyric_delay_adjust_dialog.dart';
|
import 'package:spotube/components/lyrics/lyric_delay_adjust_dialog.dart';
|
||||||
import 'package:spotube/hooks/use_auto_scroll_controller.dart';
|
import 'package:spotube/hooks/use_auto_scroll_controller.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoints.dart';
|
import 'package:spotube/hooks/use_breakpoints.dart';
|
||||||
import 'package:spotube/hooks/use_synced_lyrics.dart';
|
import 'package:spotube/hooks/use_synced_lyrics.dart';
|
||||||
import 'package:spotube/models/lyrics.dart';
|
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:spotube/provider/playlist_queue_provider.dart';
|
import 'package:spotube/provider/playlist_queue_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
@ -44,7 +42,8 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
final controller = useAutoScrollController();
|
final controller = useAutoScrollController();
|
||||||
|
|
||||||
final timedLyricsQuery = useQueries.lyrics.synced(playlist?.activeTrack);
|
final timedLyricsQuery =
|
||||||
|
useQueries.lyrics.spotifySynced(ref, playlist?.activeTrack);
|
||||||
final lyricValue = timedLyricsQuery.data;
|
final lyricValue = timedLyricsQuery.data;
|
||||||
final lyricsMap = useMemoized(
|
final lyricsMap = useMemoized(
|
||||||
() =>
|
() =>
|
||||||
@ -56,6 +55,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
[lyricValue],
|
[lyricValue],
|
||||||
);
|
);
|
||||||
final currentTime = useSyncedLyrics(ref, lyricsMap, lyricDelay);
|
final currentTime = useSyncedLyrics(ref, lyricsMap, lyricDelay);
|
||||||
|
final textZoomLevel = useState<int>(100);
|
||||||
|
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
@ -128,7 +128,8 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
fontWeight: isActive
|
fontWeight: isActive
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
fontSize: isActive ? 30 : 26,
|
fontSize: (isActive ? 30 : 26) *
|
||||||
|
(textZoomLevel.value / 100),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
lyricSlice.text,
|
lyricSlice.text,
|
||||||
@ -169,6 +170,15 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: ZoomControls(
|
||||||
|
value: textZoomLevel.value,
|
||||||
|
onChanged: (value) => textZoomLevel.value = value,
|
||||||
|
min: 50,
|
||||||
|
max: 200,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/extensions/map.dart';
|
||||||
|
import 'package:spotube/hooks/use_spotify_query.dart';
|
||||||
import 'package:spotube/models/lyrics.dart';
|
import 'package:spotube/models/lyrics.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class LyricsQueries {
|
class LyricsQueries {
|
||||||
const LyricsQueries();
|
const LyricsQueries();
|
||||||
@ -48,4 +54,61 @@ class LyricsQueries {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Concept behind this method was shamelessly stolen from
|
||||||
|
/// https://github.com/akashrchandran/spotify-lyrics-api
|
||||||
|
///
|
||||||
|
/// Thanks to [akashrchandran](https://github.com/akashrchandran) for the idea
|
||||||
|
///
|
||||||
|
/// Special thanks to [raptag](https://github.com/raptag) for discovering this
|
||||||
|
/// jem
|
||||||
|
|
||||||
|
Query<SubtitleSimple, dynamic> spotifySynced(WidgetRef ref, Track? track) {
|
||||||
|
return useSpotifyQuery<SubtitleSimple, dynamic>(
|
||||||
|
"spotify-synced-lyrics/${track?.id}}",
|
||||||
|
(spotify) async {
|
||||||
|
if (track == null) {
|
||||||
|
throw "No track currently";
|
||||||
|
}
|
||||||
|
final token = await spotify.getCredentials();
|
||||||
|
final res = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"https://spclient.wg.spotify.com/color-lyrics/v2/track/${track.id}?format=json&market=from_token",
|
||||||
|
),
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36",
|
||||||
|
"App-platform": "WebPlayer",
|
||||||
|
"authorization": "Bearer ${token.accessToken}"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw Exception("Unable to find lyrics");
|
||||||
|
}
|
||||||
|
final linesRaw = Map.castFrom<dynamic, dynamic, String, dynamic>(
|
||||||
|
jsonDecode(res.body),
|
||||||
|
)["lyrics"]?["lines"] as List?;
|
||||||
|
|
||||||
|
final lines = linesRaw?.map((line) {
|
||||||
|
return LyricSlice(
|
||||||
|
time: Duration(milliseconds: int.parse(line["startTimeMs"])),
|
||||||
|
text: line["words"] as String,
|
||||||
|
);
|
||||||
|
}).toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
return SubtitleSimple(
|
||||||
|
lyrics: lines,
|
||||||
|
name: track.name!,
|
||||||
|
uri: res.request!.url,
|
||||||
|
rating: 100,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
jsonConfig: JsonConfig(
|
||||||
|
fromJson: (json) => SubtitleSimple.fromJson(json.castKeyDeep<String>()),
|
||||||
|
toJson: (data) => data.toJson(),
|
||||||
|
),
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "packages/fl_query"
|
path: "packages/fl_query"
|
||||||
ref: new-architecture
|
ref: new-architecture
|
||||||
resolved-ref: "0c819d4e11572d592b5334280b8b4f2657f21459"
|
resolved-ref: "5332bd16d389e703b0eaf17ab79bd59382500d08"
|
||||||
url: "https://github.com/KRTirtho/fl-query.git"
|
url: "https://github.com/KRTirtho/fl-query.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
@ -544,7 +544,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "packages/fl_query_hooks"
|
path: "packages/fl_query_hooks"
|
||||||
ref: new-architecture
|
ref: new-architecture
|
||||||
resolved-ref: "0c819d4e11572d592b5334280b8b4f2657f21459"
|
resolved-ref: "5332bd16d389e703b0eaf17ab79bd59382500d08"
|
||||||
url: "https://github.com/KRTirtho/fl-query.git"
|
url: "https://github.com/KRTirtho/fl-query.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user