mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 15:35:17 +00:00
feat(lyrics): tabs for both synced and static lyrics #182
refactor: remove code-style warnings
This commit is contained in:
parent
0b79a1181c
commit
6b6907af3f
@ -1,4 +1,3 @@
|
||||
import 'package:fl_query/fl_query.dart';
|
||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -12,7 +11,6 @@ import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:spotube/models/CurrentPlaylist.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
@ -56,16 +54,11 @@ class AlbumView extends HookConsumerWidget {
|
||||
Playback playback = ref.watch(playbackProvider);
|
||||
|
||||
final SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||
final Auth auth = ref.watch(authProvider);
|
||||
|
||||
final tracksSnapshot = useQuery(
|
||||
job: albumTracksQueryJob(album.id!),
|
||||
externalData: spotify,
|
||||
);
|
||||
final albumSavedSnapshot = useQuery(
|
||||
job: albumIsSavedForCurrentUserQueryJob(album.id!),
|
||||
externalData: spotify,
|
||||
);
|
||||
|
||||
final albumArt = useMemoized(
|
||||
() => TypeConversionUtils.image_X_UrlString(
|
||||
|
@ -1,197 +0,0 @@
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||
import 'package:flutter/material.dart' hide Page;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart' hide Image, Player, Search;
|
||||
|
||||
import 'package:spotube/components/Category/CategoryCard.dart';
|
||||
import 'package:spotube/components/Home/Sidebar.dart';
|
||||
import 'package:spotube/components/Home/SpotubeNavigationBar.dart';
|
||||
import 'package:spotube/components/LoaderShimmers/ShimmerCategories.dart';
|
||||
import 'package:spotube/components/Lyrics/SyncedLyrics.dart';
|
||||
import 'package:spotube/components/Search/Search.dart';
|
||||
import 'package:spotube/components/Shared/ReplaceDownloadedFileDialog.dart';
|
||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||
import 'package:spotube/components/Player/Player.dart';
|
||||
import 'package:spotube/components/Library/UserLibrary.dart';
|
||||
import 'package:spotube/components/Shared/Waypoint.dart';
|
||||
import 'package:spotube/hooks/useBreakpointValue.dart';
|
||||
import 'package:spotube/hooks/useUpdateChecker.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/provider/Downloader.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
final selectedIndexState = StateProvider((ref) => 0);
|
||||
|
||||
class Home extends HookConsumerWidget {
|
||||
Home({Key? key}) : super(key: key);
|
||||
final logger = getLogger(Home);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final double titleBarWidth = useBreakpointValue(
|
||||
sm: 0.0,
|
||||
md: 80.0,
|
||||
lg: 256.0,
|
||||
xl: 256.0,
|
||||
xxl: 256.0,
|
||||
);
|
||||
final extended = ref.watch(sidebarExtendedStateProvider);
|
||||
final selectedIndex = ref.watch(selectedIndexState);
|
||||
onSelectedIndexChanged(int index) =>
|
||||
ref.read(selectedIndexState.notifier).state = index;
|
||||
|
||||
final downloader = ref.watch(downloaderProvider);
|
||||
final isMounted = useIsMounted();
|
||||
|
||||
useEffect(() {
|
||||
downloader.onFileExists = (track) async {
|
||||
if (!isMounted()) return false;
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => ReplaceDownloadedFileDialog(
|
||||
track: track,
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
};
|
||||
return null;
|
||||
}, [downloader]);
|
||||
|
||||
// checks for latest version of the application
|
||||
useUpdateChecker(ref);
|
||||
|
||||
final titleBarContents = Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: extended == null
|
||||
? titleBarWidth
|
||||
: (extended ? 256 : 80),
|
||||
),
|
||||
color: Theme.of(context).navigationRailTheme.backgroundColor,
|
||||
child: MoveWindow(),
|
||||
),
|
||||
Expanded(child: MoveWindow()),
|
||||
if (!kIsMacOS && !kIsMobile) const TitleBarActionButtons(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final backgroundColor = Theme.of(context).backgroundColor;
|
||||
|
||||
useEffect(() {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: backgroundColor, // status bar color
|
||||
statusBarIconBrightness: backgroundColor.computeLuminance() > 0.179
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}, [backgroundColor]);
|
||||
|
||||
return Scaffold(
|
||||
bottomNavigationBar: SpotubeNavigationBar(
|
||||
selectedIndex: selectedIndex,
|
||||
onSelectedIndexChanged: onSelectedIndexChanged,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
if (selectedIndex != 3)
|
||||
kIsMobile
|
||||
? titleBarContents
|
||||
: WindowTitleBarBox(child: titleBarContents),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Sidebar(
|
||||
selectedIndex: selectedIndex,
|
||||
onSelectedIndexChanged: onSelectedIndexChanged,
|
||||
),
|
||||
// contents of the spotify
|
||||
if (selectedIndex == 0)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 8.0,
|
||||
top: 8.0,
|
||||
left: 8.0,
|
||||
),
|
||||
child: HookBuilder(builder: (context) {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final recommendationMarket = ref.watch(
|
||||
userPreferencesProvider
|
||||
.select((s) => s.recommendationMarket),
|
||||
);
|
||||
|
||||
final categoriesQuery = useInfiniteQuery(
|
||||
job: categoriesQueryJob,
|
||||
externalData: {
|
||||
"spotify": spotify,
|
||||
"recommendationMarket": recommendationMarket,
|
||||
},
|
||||
);
|
||||
|
||||
final categories = [
|
||||
useMemoized(
|
||||
() => Category()
|
||||
..id = "user-featured-playlists"
|
||||
..name = "Featured",
|
||||
[],
|
||||
),
|
||||
...categoriesQuery.pages
|
||||
.expand<Category?>(
|
||||
(page) => page?.items ?? const Iterable.empty(),
|
||||
)
|
||||
.toList()
|
||||
];
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = categories[index];
|
||||
if (category == null) return Container();
|
||||
if (index == categories.length - 1) {
|
||||
return Waypoint(
|
||||
onEnter: () {
|
||||
if (categoriesQuery.hasNextPage) {
|
||||
categoriesQuery.fetchNextPage();
|
||||
}
|
||||
},
|
||||
child: const ShimmerCategories(),
|
||||
);
|
||||
}
|
||||
return CategoryCard(category);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (selectedIndex == 1) const Search(),
|
||||
if (selectedIndex == 2) const UserLibrary(),
|
||||
if (selectedIndex == 3) const SyncedLyrics(),
|
||||
],
|
||||
),
|
||||
),
|
||||
// player itself
|
||||
Player(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
@ -193,7 +192,7 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
SortTracksDropdown(
|
||||
value: sortBy.value,
|
||||
onChanged: (value) {
|
||||
if (value != null) sortBy.value = value;
|
||||
sortBy.value = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/components/Login/TokenLoginForms.dart';
|
||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
class TokenLogin extends HookConsumerWidget {
|
||||
const TokenLogin({Key? key}) : super(key: key);
|
||||
|
114
lib/components/Lyrics/GeniusLyrics.dart
Normal file
114
lib/components/Lyrics/GeniusLyrics.dart
Normal file
@ -0,0 +1,114 @@
|
||||
import 'package:fl_query_hooks/fl_query_hooks.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/LoaderShimmers/ShimmerLyrics.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class GeniusLyrics extends HookConsumerWidget {
|
||||
final PaletteColor palette;
|
||||
const GeniusLyrics({
|
||||
required this.palette,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
Playback playback = ref.watch(playbackProvider);
|
||||
final geniusLyricsQuery = useQuery(
|
||||
job: geniusLyricsQueryJob,
|
||||
externalData: Tuple2(
|
||||
playback.track,
|
||||
ref.watch(userPreferencesProvider).geniusAccessToken,
|
||||
),
|
||||
);
|
||||
final breakpoint = useBreakpoints();
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
useEffect(() {
|
||||
if (playback.track != null) {
|
||||
geniusLyricsQuery.setExternalData(Tuple2(
|
||||
playback.track,
|
||||
ref.read(userPreferencesProvider).geniusAccessToken,
|
||||
));
|
||||
geniusLyricsQuery.refetch();
|
||||
}
|
||||
return null;
|
||||
}, [playback.track]);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
playback.track?.name ?? "",
|
||||
style: breakpoint >= Breakpoints.md
|
||||
? textTheme.headline3
|
||||
: textTheme.headline4?.copyWith(
|
||||
fontSize: 25,
|
||||
color: palette.titleTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
TypeConversionUtils.artists_X_String<Artist>(
|
||||
playback.track?.artists ?? []),
|
||||
style: (breakpoint >= Breakpoints.md
|
||||
? textTheme.headline5
|
||||
: textTheme.headline6)
|
||||
?.copyWith(color: palette.bodyTextColor),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (geniusLyricsQuery.isLoading) {
|
||||
return const ShimmerLyrics();
|
||||
} else if (geniusLyricsQuery.hasError) {
|
||||
return Text(
|
||||
"Sorry, no Lyrics were found for `${playback.track?.name}` :'(\n${geniusLyricsQuery.error.toString()}",
|
||||
style: textTheme.bodyText1?.copyWith(
|
||||
color: palette.bodyTextColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final lyrics = geniusLyricsQuery.data;
|
||||
|
||||
return Text(
|
||||
lyrics == null && playback.track == null
|
||||
? "No Track being played currently"
|
||||
: lyrics ?? "",
|
||||
style: textTheme.headline6?.copyWith(
|
||||
color: palette.bodyTextColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text("Powered by genius.com"),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,93 +1,73 @@
|
||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/LoaderShimmers/ShimmerLyrics.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/components/Lyrics/GeniusLyrics.dart';
|
||||
import 'package:spotube/components/Lyrics/SyncedLyrics.dart';
|
||||
import 'package:spotube/components/Shared/UniversalImage.dart';
|
||||
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Lyrics extends HookConsumerWidget {
|
||||
final Color? titleBarForegroundColor;
|
||||
const Lyrics({
|
||||
required this.titleBarForegroundColor,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
const Lyrics({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
Playback playback = ref.watch(playbackProvider);
|
||||
final geniusLyricsQuery = useQuery(
|
||||
job: geniusLyricsQueryJob,
|
||||
externalData: Tuple2(
|
||||
playback.track,
|
||||
ref.watch(userPreferencesProvider).geniusAccessToken,
|
||||
String albumArt = useMemoized(
|
||||
() => TypeConversionUtils.image_X_UrlString(
|
||||
playback.track?.album?.images,
|
||||
index: (playback.track?.album?.images?.length ?? 1) - 1,
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
),
|
||||
[playback.track?.album?.images],
|
||||
);
|
||||
final breakpoint = useBreakpoints();
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final palette = usePaletteColor(albumArt, ref);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
playback.track?.name ?? "",
|
||||
style: breakpoint >= Breakpoints.md
|
||||
? textTheme.headline3
|
||||
: textTheme.headline4?.copyWith(fontSize: 25),
|
||||
),
|
||||
useCustomStatusBarColor(
|
||||
palette.color,
|
||||
true,
|
||||
noSetBGColor: true,
|
||||
);
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: const TabBar(
|
||||
isScrollable: true,
|
||||
tabs: [
|
||||
Tab(text: "Synced Lyrics"),
|
||||
Tab(text: "Lyrics (genius.com)"),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
TypeConversionUtils.artists_X_String<Artist>(
|
||||
playback.track?.artists ?? []),
|
||||
style: breakpoint >= Breakpoints.md
|
||||
? textTheme.headline5
|
||||
: textTheme.headline6,
|
||||
body: Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(albumArt),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (geniusLyricsQuery.isLoading) {
|
||||
return const ShimmerLyrics();
|
||||
} else if (geniusLyricsQuery.hasError) {
|
||||
return Text(
|
||||
"Sorry, no Lyrics were found for `${playback.track?.name}` :'(\n${geniusLyricsQuery.error.toString()}",
|
||||
);
|
||||
}
|
||||
|
||||
final lyrics = geniusLyricsQuery.data;
|
||||
|
||||
return Text(
|
||||
lyrics == null && playback.track == null
|
||||
? "No Track being played currently"
|
||||
: lyrics ?? "",
|
||||
style: textTheme.headline6
|
||||
?.copyWith(color: textTheme.headline1?.color),
|
||||
);
|
||||
},
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: Container(
|
||||
color: palette.color.withOpacity(.7),
|
||||
child: SafeArea(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
SyncedLyrics(palette: palette),
|
||||
GeniusLyrics(palette: palette),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text("Powered by genius.com"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,14 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:fl_query_hooks/fl_query_hooks.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/LoaderShimmers/ShimmerLyrics.dart';
|
||||
import 'package:spotube/components/Lyrics/LyricDelayAdjustDialog.dart';
|
||||
import 'package:spotube/components/Lyrics/Lyrics.dart';
|
||||
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
||||
import 'package:spotube/components/Shared/UniversalImage.dart';
|
||||
import 'package:spotube/hooks/useAutoScrollController.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||
import 'package:spotube/hooks/useSyncedLyrics.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
@ -27,7 +22,11 @@ final lyricDelayState = StateProvider<Duration>(
|
||||
);
|
||||
|
||||
class SyncedLyrics extends HookConsumerWidget {
|
||||
const SyncedLyrics({Key? key}) : super(key: key);
|
||||
final PaletteColor palette;
|
||||
const SyncedLyrics({
|
||||
required this.palette,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
@ -40,7 +39,6 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
|
||||
final breakpoint = useBreakpoints();
|
||||
final controller = useAutoScrollController();
|
||||
final failed = useState(false);
|
||||
final lyricValue = timedLyricsQuery.data;
|
||||
final lyricsMap = useMemoized(
|
||||
() =>
|
||||
@ -61,197 +59,111 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(lyricDelayState.notifier).state = Duration.zero;
|
||||
});
|
||||
failed.value = false;
|
||||
return null;
|
||||
}, [playback.track]);
|
||||
|
||||
useEffect(() {
|
||||
if (lyricValue != null && lyricValue.rating <= 2) {
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text("No"),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text("Yes"),
|
||||
onPressed: () {
|
||||
failed.value = true;
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Text(
|
||||
"The found lyrics might not be properly synced. Do you want to default to static (genius.com) lyrics?",
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
"Hint: Wait for a moment to see if the lyric actually sync. Sometimes it may sync.",
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [lyricValue]);
|
||||
|
||||
// when synced lyrics not found, fallback to GeniusLyrics
|
||||
|
||||
String albumArt = useMemoized(
|
||||
() => TypeConversionUtils.image_X_UrlString(
|
||||
playback.track?.album?.images,
|
||||
index: (playback.track?.album?.images?.length ?? 1) - 1,
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
),
|
||||
[playback.track?.album?.images],
|
||||
);
|
||||
final palette = usePaletteColor(albumArt, ref);
|
||||
|
||||
final headlineTextStyle = (breakpoint >= Breakpoints.md
|
||||
? textTheme.headline3
|
||||
: textTheme.headline4?.copyWith(fontSize: 25))
|
||||
?.copyWith(color: palette.titleTextColor);
|
||||
|
||||
useCustomStatusBarColor(
|
||||
palette.color,
|
||||
true,
|
||||
noSetBGColor: true,
|
||||
);
|
||||
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(albumArt),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: Container(
|
||||
color: palette.color.withOpacity(.7),
|
||||
child: SafeArea(
|
||||
child: failed.value
|
||||
? Lyrics(titleBarForegroundColor: palette.bodyTextColor)
|
||||
: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: breakpoint >= Breakpoints.md ? 50 : 30,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: SpotubeMarqueeText(
|
||||
text: playback.track?.name ?? "Not Playing",
|
||||
style: headlineTextStyle,
|
||||
isHovering: true,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
tooltip: "Lyrics Delay",
|
||||
icon: const Icon(Icons.av_timer_rounded),
|
||||
onPressed: () async {
|
||||
final delay = await showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
const LyricDelayAdjustDialog(),
|
||||
);
|
||||
if (delay != null) {
|
||||
ref
|
||||
.read(lyricDelayState.notifier)
|
||||
.state = delay;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
TypeConversionUtils.artists_X_String<Artist>(
|
||||
playback.track?.artists ?? []),
|
||||
style: breakpoint >= Breakpoints.md
|
||||
? textTheme.headline5
|
||||
: textTheme.headline6,
|
||||
),
|
||||
),
|
||||
if (lyricValue != null && lyricValue.lyrics.isNotEmpty)
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: lyricValue.lyrics.length,
|
||||
itemBuilder: (context, index) {
|
||||
final lyricSlice = lyricValue.lyrics[index];
|
||||
final isActive =
|
||||
lyricSlice.time.inSeconds == currentTime;
|
||||
|
||||
if (isActive) {
|
||||
controller.scrollToIndex(
|
||||
index,
|
||||
preferPosition: AutoScrollPosition.middle,
|
||||
);
|
||||
}
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(index),
|
||||
index: index,
|
||||
controller: controller,
|
||||
child: lyricSlice.text.isEmpty
|
||||
? Container()
|
||||
: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(
|
||||
milliseconds: 250),
|
||||
style: TextStyle(
|
||||
color: isActive
|
||||
? Colors.white
|
||||
: palette.bodyTextColor,
|
||||
fontWeight: isActive
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
fontSize: isActive ? 30 : 26,
|
||||
),
|
||||
child: Text(
|
||||
lyricSlice.text,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (playback.track != null &&
|
||||
(lyricValue == null ||
|
||||
lyricValue.lyrics.isEmpty == true))
|
||||
const Expanded(child: ShimmerLyrics()),
|
||||
],
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: breakpoint >= Breakpoints.md ? 50 : 30,
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Stack(
|
||||
children: [
|
||||
Center(
|
||||
child: SpotubeMarqueeText(
|
||||
text: playback.track?.name ?? "Not Playing",
|
||||
style: headlineTextStyle,
|
||||
isHovering: true,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
tooltip: "Lyrics Delay",
|
||||
icon: const Icon(Icons.av_timer_rounded),
|
||||
onPressed: () async {
|
||||
final delay = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => const LyricDelayAdjustDialog(),
|
||||
);
|
||||
if (delay != null) {
|
||||
ref.read(lyricDelayState.notifier).state = delay;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
TypeConversionUtils.artists_X_String<Artist>(
|
||||
playback.track?.artists ?? []),
|
||||
style: breakpoint >= Breakpoints.md
|
||||
? textTheme.headline5
|
||||
: textTheme.headline6,
|
||||
),
|
||||
),
|
||||
if (lyricValue != null && lyricValue.lyrics.isNotEmpty)
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: lyricValue.lyrics.length,
|
||||
itemBuilder: (context, index) {
|
||||
final lyricSlice = lyricValue.lyrics[index];
|
||||
final isActive = lyricSlice.time.inSeconds == currentTime;
|
||||
|
||||
if (isActive) {
|
||||
controller.scrollToIndex(
|
||||
index,
|
||||
preferPosition: AutoScrollPosition.middle,
|
||||
);
|
||||
}
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(index),
|
||||
index: index,
|
||||
controller: controller,
|
||||
child: lyricSlice.text.isEmpty
|
||||
? Container()
|
||||
: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
style: TextStyle(
|
||||
color: isActive
|
||||
? Colors.white
|
||||
: palette.bodyTextColor,
|
||||
fontWeight: isActive
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
fontSize: isActive ? 30 : 26,
|
||||
),
|
||||
child: Text(
|
||||
lyricSlice.text,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (playback.track != null &&
|
||||
(lyricValue == null || lyricValue.lyrics.isEmpty == true))
|
||||
const Expanded(child: ShimmerLyrics()),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,6 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
|
||||
return TextButton(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.add_box_rounded, size: 50),
|
||||
Text("Create Playlist", style: TextStyle(fontSize: 22)),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -106,6 +98,14 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(horizontal: 15, vertical: 100)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.add_box_rounded, size: 50),
|
||||
Text("Create Playlist", style: TextStyle(fontSize: 22)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class About extends HookWidget {
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
leading: Icon(Icons.info_outline_rounded),
|
||||
leading: const Icon(Icons.info_outline_rounded),
|
||||
title: const Text("About Spotube"),
|
||||
onTap: () {
|
||||
showAboutDialog(
|
||||
|
@ -475,8 +475,8 @@ class Settings extends HookConsumerWidget {
|
||||
icon: const Icon(Icons.favorite_outline_rounded),
|
||||
label: const Text("Please Sponsor/Donate"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.red[100],
|
||||
onPrimary: Colors.pinkAccent,
|
||||
backgroundColor: Colors.red[100],
|
||||
foregroundColor: Colors.pinkAccent,
|
||||
padding: const EdgeInsets.all(15),
|
||||
),
|
||||
onPressed: () {
|
||||
|
@ -23,6 +23,9 @@ class AnchorButton<T> extends HookWidget {
|
||||
var tap = useState(false);
|
||||
|
||||
return GestureDetector(
|
||||
onTapDown: (event) => tap.value = true,
|
||||
onTapUp: (event) => tap.value = false,
|
||||
onTap: onTap,
|
||||
child: MouseRegion(
|
||||
cursor: MaterialStateMouseCursor.clickable,
|
||||
child: Text(
|
||||
@ -37,9 +40,6 @@ class AnchorButton<T> extends HookWidget {
|
||||
onEnter: (event) => hover.value = true,
|
||||
onExit: (event) => hover.value = false,
|
||||
),
|
||||
onTapDown: (event) => tap.value = true,
|
||||
onTapUp: (event) => tap.value = false,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ColoredTabBar extends ColoredBox implements PreferredSizeWidget {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
final TabBar child;
|
||||
|
||||
const ColoredTabBar({
|
||||
|
@ -62,12 +62,12 @@ class DownloadConfirmationDialog extends StatelessWidget {
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text("Accept"),
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.red,
|
||||
onPrimary: Colors.white,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text("Accept"),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotube/components/Shared/AnchorButton.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class Hyperlink extends StatelessWidget {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
@ -139,10 +139,10 @@ class Spotube extends StatefulHookConsumerWidget {
|
||||
const Spotube({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SpotubeState createState() => _SpotubeState();
|
||||
SpotubeState createState() => SpotubeState();
|
||||
}
|
||||
|
||||
class _SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
|
||||
class SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
|
||||
final logger = getLogger(Spotube);
|
||||
SharedPreferences? localStorage;
|
||||
|
||||
|
@ -8,7 +8,7 @@ import 'package:spotube/components/Home/Shell.dart';
|
||||
import 'package:spotube/components/Library/UserLibrary.dart';
|
||||
import 'package:spotube/components/Login/LoginTutorial.dart';
|
||||
import 'package:spotube/components/Login/TokenLogin.dart';
|
||||
import 'package:spotube/components/Lyrics/SyncedLyrics.dart';
|
||||
import 'package:spotube/components/Lyrics/Lyrics.dart';
|
||||
import 'package:spotube/components/Player/PlayerView.dart';
|
||||
import 'package:spotube/components/Playlist/PlaylistView.dart';
|
||||
import 'package:spotube/components/Search/Search.dart';
|
||||
@ -44,8 +44,7 @@ final router = GoRouter(
|
||||
GoRoute(
|
||||
path: "/lyrics",
|
||||
name: "Lyrics",
|
||||
pageBuilder: (context, state) =>
|
||||
const SpotubePage(child: SyncedLyrics()),
|
||||
pageBuilder: (context, state) => const SpotubePage(child: Lyrics()),
|
||||
),
|
||||
GoRoute(
|
||||
path: "/settings",
|
||||
|
@ -4,8 +4,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/Home/Home.dart';
|
||||
import 'package:spotube/components/Player/PlayerControls.dart';
|
||||
import 'package:spotube/models/GoRouteDeclarations.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
@ -80,19 +80,18 @@ class HomeTabIntent extends Intent {
|
||||
class HomeTabAction extends Action<HomeTabIntent> {
|
||||
@override
|
||||
invoke(intent) {
|
||||
final notifier = intent.ref.read(selectedIndexState.notifier);
|
||||
switch (intent.tab) {
|
||||
case HomeTabs.browse:
|
||||
notifier.state = 0;
|
||||
router.go("/");
|
||||
break;
|
||||
case HomeTabs.search:
|
||||
notifier.state = 1;
|
||||
router.go("/search");
|
||||
break;
|
||||
case HomeTabs.library:
|
||||
notifier.state = 2;
|
||||
router.go("/library");
|
||||
break;
|
||||
case HomeTabs.lyrics:
|
||||
notifier.state = 3;
|
||||
router.go("/lyrics");
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dbus/dbus.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_query/fl_query.dart';
|
||||
import 'package:spotube/models/LyricsModels.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
|
@ -260,7 +260,7 @@ class _MprisMediaPlayer2Player extends DBusObject {
|
||||
|
||||
/// Gets value of property org.mpris.MediaPlayer2.Player.Rate
|
||||
Future<DBusMethodResponse> getRate() async {
|
||||
return DBusMethodSuccessResponse([DBusDouble(1)]);
|
||||
return DBusMethodSuccessResponse([const DBusDouble(1)]);
|
||||
}
|
||||
|
||||
/// Sets property org.mpris.MediaPlayer2.Player.Rate
|
||||
@ -442,9 +442,12 @@ class _MprisMediaPlayer2Player extends DBusObject {
|
||||
}
|
||||
|
||||
/// Emits signal org.mpris.MediaPlayer2.Player.Seeked
|
||||
Future<void> emitSeeked(int Position) async {
|
||||
Future<void> emitSeeked(int position) async {
|
||||
await emitSignal(
|
||||
'org.mpris.MediaPlayer2.Player', 'Seeked', [DBusInt64(Position)]);
|
||||
'org.mpris.MediaPlayer2.Player',
|
||||
'Seeked',
|
||||
[DBusInt64(position)],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateProperties(Playback playback) async {
|
||||
|
@ -6,7 +6,7 @@
|
||||
Duration parseDuration(String input) {
|
||||
final parts = input.split(':');
|
||||
|
||||
if (parts.length != 3) throw FormatException('Invalid time format');
|
||||
if (parts.length != 3) throw const FormatException('Invalid time format');
|
||||
|
||||
int days;
|
||||
int hours;
|
||||
@ -18,7 +18,7 @@ Duration parseDuration(String input) {
|
||||
{
|
||||
final p = parts[2].split('.');
|
||||
|
||||
if (p.length != 2) throw FormatException('Invalid time format');
|
||||
if (p.length != 2) throw const FormatException('Invalid time format');
|
||||
|
||||
final p2 = int.parse(p[1]);
|
||||
microseconds = p2 % 1000;
|
||||
@ -38,12 +38,13 @@ Duration parseDuration(String input) {
|
||||
// TODO verify that there are no negative parts
|
||||
|
||||
return Duration(
|
||||
days: days,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds,
|
||||
milliseconds: milliseconds,
|
||||
microseconds: microseconds);
|
||||
days: days,
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
seconds: seconds,
|
||||
milliseconds: milliseconds,
|
||||
microseconds: microseconds,
|
||||
);
|
||||
}
|
||||
|
||||
Duration? tryParseDuration(String input) {
|
||||
|
@ -1,24 +1,19 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart' hide Element;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/Library/UserLocalTracks.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:spotube/models/LyricsModels.dart';
|
||||
import 'package:spotube/models/SpotifySpotubeCredentials.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/models/generated_secrets.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
abstract class ServiceUtils {
|
||||
static final logger = getLogger("ServiceUtils");
|
||||
@ -179,109 +174,6 @@ abstract class ServiceUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use getAccessToken instead")
|
||||
static Future<String?> connectIpc(String authUri, String redirectUri) async {
|
||||
try {
|
||||
logger.i("[connectIpc][Launching]: $authUri");
|
||||
await launchUrl(
|
||||
Uri.parse(authUri),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
|
||||
HttpServer server = await HttpServer.bind(
|
||||
InternetAddress.loopbackIPv4,
|
||||
4304,
|
||||
shared: true,
|
||||
);
|
||||
|
||||
logger.i("[connectIpc] Server started");
|
||||
|
||||
await for (HttpRequest request in server) {
|
||||
if (request.uri.path == "/auth/spotify/callback" &&
|
||||
request.method == "GET") {
|
||||
String? code = request.uri.queryParameters["code"];
|
||||
if (code != null) {
|
||||
request.response
|
||||
..statusCode = HttpStatus.ok
|
||||
..write("Authentication successful. Now Go back to Spotube")
|
||||
..close();
|
||||
return "$redirectUri?code=$code";
|
||||
} else {
|
||||
request.response
|
||||
..statusCode = HttpStatus.forbidden
|
||||
..write("Authorization failed start over!")
|
||||
..close();
|
||||
throw Exception("No code provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e, stack) {
|
||||
logger.e("connectIpc", e, stack);
|
||||
rethrow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static const authRedirectUri = "http://localhost:4304/auth/spotify/callback";
|
||||
|
||||
/// Use [getAccessToken] instead
|
||||
/// This method will be removed in the next major release
|
||||
@Deprecated("Use getAccessToken instead")
|
||||
static Future<void> oauthLogin(Auth auth,
|
||||
{required String clientId, required String clientSecret}) async {
|
||||
try {
|
||||
String? accessToken;
|
||||
String? refreshToken;
|
||||
DateTime? expiration;
|
||||
final credentials = SpotifyApiCredentials(clientId, clientSecret);
|
||||
final grant = SpotifyApi.authorizationCodeGrant(credentials);
|
||||
|
||||
final authUri = grant.getAuthorizationUrl(
|
||||
Uri.parse(authRedirectUri),
|
||||
);
|
||||
|
||||
final responseUri = await connectIpc(authUri.toString(), authRedirectUri);
|
||||
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||
if (responseUri != null) {
|
||||
final SpotifyApi spotify =
|
||||
SpotifyApi.fromAuthCodeGrant(grant, responseUri);
|
||||
final credentials = await spotify.getCredentials();
|
||||
if (credentials.accessToken != null) {
|
||||
accessToken = credentials.accessToken;
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.accessToken, credentials.accessToken!);
|
||||
}
|
||||
if (credentials.refreshToken != null) {
|
||||
refreshToken = credentials.refreshToken;
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.refreshToken, credentials.refreshToken!);
|
||||
}
|
||||
if (credentials.expiration != null) {
|
||||
expiration = credentials.expiration;
|
||||
await localStorage.setString(LocalStorageKeys.expiration,
|
||||
credentials.expiration?.toString() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
await localStorage.setString(LocalStorageKeys.clientId, clientId);
|
||||
await localStorage.setString(
|
||||
LocalStorageKeys.clientSecret,
|
||||
clientSecret,
|
||||
);
|
||||
|
||||
// auth.setAuthState(
|
||||
// clientId: clientId,
|
||||
// clientSecret: clientSecret,
|
||||
// accessToken: accessToken,
|
||||
// refreshToken: refreshToken,
|
||||
// expiration: expiration,
|
||||
// );
|
||||
} catch (e, stack) {
|
||||
logger.e("oauthLogin", e, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static const baseUri = "https://www.rentanadviser.com/subtitles";
|
||||
|
||||
static Future<SubtitleSimple?> getTimedLyrics(SpotubeTrack track) async {
|
||||
|
@ -1300,7 +1300,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -61,6 +61,7 @@ dependencies:
|
||||
fl_query_hooks: ^0.3.1
|
||||
flutter_inappwebview: ^5.4.3+7
|
||||
tuple: ^2.0.1
|
||||
uuid: ^3.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -13,7 +13,7 @@ import 'package:spotube/main.dart';
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(Spotube());
|
||||
await tester.pumpWidget(const Spotube());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
|
Loading…
Reference in New Issue
Block a user