mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Android SafeArea issues fixed
configurations for different plugin for android added adjusted platform bound opertations
This commit is contained in:
parent
39a92a56f3
commit
c64f329c42
@ -1,34 +1,30 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
|
||||||
package="oss.krtirtho.spotube">
|
|
||||||
<application
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
android:label="spotube"
|
|
||||||
android:name="${applicationName}"
|
<queries>
|
||||||
android:icon="@mipmap/ic_launcher">
|
<!-- If your app opens https URLs -->
|
||||||
<activity
|
<intent>
|
||||||
android:name=".MainActivity"
|
<action android:name="android.intent.action.VIEW" />
|
||||||
android:exported="true"
|
<data android:scheme="https" />
|
||||||
android:launchMode="singleTop"
|
</intent>
|
||||||
android:theme="@style/LaunchTheme"
|
</queries>
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
||||||
android:hardwareAccelerated="true"
|
<application android:label="spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true">
|
||||||
android:windowSoftInputMode="adjustResize">
|
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
<intent-filter>
|
||||||
android:resource="@style/NormalTheme"
|
<action android:name="android.intent.action.MAIN" />
|
||||||
/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<intent-filter>
|
</intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
</activity>
|
||||||
</intent-filter>
|
<!-- Don't delete the meta-data below.
|
||||||
</activity>
|
|
||||||
<!-- Don't delete the meta-data below.
|
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||||
android:name="flutterEmbedding"
|
</application>
|
||||||
android:value="2" />
|
|
||||||
</application>
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.50'
|
ext.kotlin_version = '1.6.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -37,5 +37,11 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
'AUDIO_SESSION_MICROPHONE=0'
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,47 +1,54 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Sptube</string>
|
<string>Sptube</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>spotube</string>
|
<string>spotube</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true/>
|
<true />
|
||||||
</dict>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true />
|
||||||
|
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||||
|
<true />
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
@ -38,63 +38,65 @@ class AlbumView extends ConsumerWidget {
|
|||||||
|
|
||||||
var isPlaylistPlaying = playback.currentPlaylist?.id == album.id;
|
var isPlaylistPlaying = playback.currentPlaylist?.id == album.id;
|
||||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
body: FutureBuilder<Iterable<TrackSimple>>(
|
child: Scaffold(
|
||||||
future: spotify.albums.getTracks(album.id!).all(),
|
body: FutureBuilder<Iterable<TrackSimple>>(
|
||||||
builder: (context, snapshot) {
|
future: spotify.albums.getTracks(album.id!).all(),
|
||||||
List<Track> tracks = snapshot.data?.map((trackSmp) {
|
builder: (context, snapshot) {
|
||||||
return simpleTrackToTrack(trackSmp, album);
|
List<Track> tracks = snapshot.data?.map((trackSmp) {
|
||||||
}).toList() ??
|
return simpleTrackToTrack(trackSmp, album);
|
||||||
[];
|
}).toList() ??
|
||||||
return Column(
|
[];
|
||||||
children: [
|
return Column(
|
||||||
PageWindowTitleBar(
|
children: [
|
||||||
leading: Row(
|
PageWindowTitleBar(
|
||||||
children: [
|
leading: Row(
|
||||||
// nav back
|
children: [
|
||||||
const BackButton(),
|
// nav back
|
||||||
// heart playlist
|
const BackButton(),
|
||||||
IconButton(
|
// heart playlist
|
||||||
icon: const Icon(Icons.favorite_outline_rounded),
|
IconButton(
|
||||||
onPressed: () {},
|
icon: const Icon(Icons.favorite_outline_rounded),
|
||||||
),
|
onPressed: () {},
|
||||||
// play playlist
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
isPlaylistPlaying
|
|
||||||
? Icons.stop_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
),
|
||||||
onPressed: snapshot.hasData
|
// play playlist
|
||||||
? () => playPlaylist(playback, tracks)
|
IconButton(
|
||||||
: null,
|
icon: Icon(
|
||||||
)
|
isPlaylistPlaying
|
||||||
],
|
? Icons.stop_rounded
|
||||||
),
|
: Icons.play_arrow_rounded,
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Text(album.name!,
|
|
||||||
style: Theme.of(context).textTheme.headline4),
|
|
||||||
),
|
|
||||||
snapshot.hasError
|
|
||||||
? const Center(child: Text("Error occurred"))
|
|
||||||
: !snapshot.hasData
|
|
||||||
? const Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator.adaptive()),
|
|
||||||
)
|
|
||||||
: TracksTableView(
|
|
||||||
tracks,
|
|
||||||
onTrackPlayButtonPressed: (currentTrack) =>
|
|
||||||
playPlaylist(
|
|
||||||
playback,
|
|
||||||
tracks,
|
|
||||||
currentTrack: currentTrack,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
onPressed: snapshot.hasData
|
||||||
);
|
? () => playPlaylist(playback, tracks)
|
||||||
}),
|
: null,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(album.name!,
|
||||||
|
style: Theme.of(context).textTheme.headline4),
|
||||||
|
),
|
||||||
|
snapshot.hasError
|
||||||
|
? const Center(child: Text("Error occurred"))
|
||||||
|
: !snapshot.hasData
|
||||||
|
? const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive()),
|
||||||
|
)
|
||||||
|
: TracksTableView(
|
||||||
|
tracks,
|
||||||
|
onTrackPlayButtonPressed: (currentTrack) =>
|
||||||
|
playPlaylist(
|
||||||
|
playback,
|
||||||
|
tracks,
|
||||||
|
currentTrack: currentTrack,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,32 +59,34 @@ class _ArtistAlbumViewState extends ConsumerState<ArtistAlbumView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
appBar: const PageWindowTitleBar(leading: BackButton()),
|
child: Scaffold(
|
||||||
body: Column(
|
appBar: const PageWindowTitleBar(leading: BackButton()),
|
||||||
children: [
|
body: Column(
|
||||||
Text(
|
children: [
|
||||||
widget.artistName,
|
Text(
|
||||||
style: Theme.of(context).textTheme.headline4,
|
widget.artistName,
|
||||||
),
|
style: Theme.of(context).textTheme.headline4,
|
||||||
Expanded(
|
),
|
||||||
child: PagedGridView(
|
Expanded(
|
||||||
pagingController: _pagingController,
|
child: PagedGridView(
|
||||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
pagingController: _pagingController,
|
||||||
maxCrossAxisExtent: 260,
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
childAspectRatio: 9 / 13,
|
maxCrossAxisExtent: 260,
|
||||||
crossAxisSpacing: 20,
|
childAspectRatio: 9 / 13,
|
||||||
mainAxisSpacing: 20,
|
crossAxisSpacing: 20,
|
||||||
),
|
mainAxisSpacing: 20,
|
||||||
padding: const EdgeInsets.all(10),
|
),
|
||||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
padding: const EdgeInsets.all(10),
|
||||||
itemBuilder: (context, item, index) {
|
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||||
return AlbumCard(item);
|
itemBuilder: (context, item, index) {
|
||||||
},
|
return AlbumCard(item);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -45,270 +45,273 @@ class ArtistProfile extends HookConsumerWidget {
|
|||||||
|
|
||||||
final breakpoint = useBreakpoints();
|
final breakpoint = useBreakpoints();
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
appBar: const PageWindowTitleBar(
|
child: Scaffold(
|
||||||
leading: BackButton(),
|
appBar: const PageWindowTitleBar(
|
||||||
),
|
leading: BackButton(),
|
||||||
body: FutureBuilder<Artist>(
|
),
|
||||||
future: spotify.artists.get(artistId),
|
body: FutureBuilder<Artist>(
|
||||||
builder: (context, snapshot) {
|
future: spotify.artists.get(artistId),
|
||||||
if (!snapshot.hasData) {
|
builder: (context, snapshot) {
|
||||||
return const Center(child: CircularProgressIndicator.adaptive());
|
if (!snapshot.hasData) {
|
||||||
}
|
return const Center(child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
controller: parentScrollController,
|
controller: parentScrollController,
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Wrap(
|
Wrap(
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
runAlignment: WrapAlignment.center,
|
runAlignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 50),
|
const SizedBox(width: 50),
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: avatarWidth,
|
radius: avatarWidth,
|
||||||
backgroundImage: CachedNetworkImageProvider(
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
imageToUrlString(snapshot.data!.images),
|
imageToUrlString(snapshot.data!.images),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.all(20),
|
||||||
padding: const EdgeInsets.all(20),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Container(
|
padding: const EdgeInsets.symmetric(
|
||||||
padding: const EdgeInsets.symmetric(
|
horizontal: 10, vertical: 5),
|
||||||
horizontal: 10, vertical: 5),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: Colors.blue,
|
||||||
color: Colors.blue,
|
borderRadius: BorderRadius.circular(50)),
|
||||||
borderRadius: BorderRadius.circular(50)),
|
child: Text(snapshot.data!.type!.toUpperCase(),
|
||||||
child: Text(snapshot.data!.type!.toUpperCase(),
|
style: chipTextVariant?.copyWith(
|
||||||
style: chipTextVariant?.copyWith(
|
color: Colors.white)),
|
||||||
color: Colors.white)),
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
snapshot.data!.name!,
|
||||||
snapshot.data!.name!,
|
style: breakpoint.isSm
|
||||||
style: breakpoint.isSm
|
? textTheme.headline4
|
||||||
? textTheme.headline4
|
: textTheme.headline2,
|
||||||
: textTheme.headline2,
|
),
|
||||||
),
|
Text(
|
||||||
Text(
|
"${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers",
|
||||||
"${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers",
|
style: breakpoint.isSm
|
||||||
style: breakpoint.isSm
|
? textTheme.bodyText1
|
||||||
? textTheme.bodyText1
|
: textTheme.headline5,
|
||||||
: textTheme.headline5,
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
const SizedBox(height: 20),
|
Row(
|
||||||
Row(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
// TODO: Implement check if user follows this artist
|
||||||
// TODO: Implement check if user follows this artist
|
// LIMITATION: spotify-dart lib
|
||||||
// LIMITATION: spotify-dart lib
|
FutureBuilder(
|
||||||
FutureBuilder(
|
future: Future.value(true),
|
||||||
future: Future.value(true),
|
builder: (context, snapshot) {
|
||||||
builder: (context, snapshot) {
|
return OutlinedButton(
|
||||||
return OutlinedButton(
|
onPressed: () async {
|
||||||
onPressed: () async {
|
// TODO: make `follow/unfollow` artists button work
|
||||||
// TODO: make `follow/unfollow` artists button work
|
// LIMITATION: spotify-dart lib
|
||||||
// LIMITATION: spotify-dart lib
|
},
|
||||||
},
|
child: Text(snapshot.data == true
|
||||||
child: Text(snapshot.data == true
|
? "Following"
|
||||||
? "Following"
|
: "Follow"),
|
||||||
: "Follow"),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
IconButton(
|
||||||
IconButton(
|
icon: const Icon(Icons.share_rounded),
|
||||||
icon: const Icon(Icons.share_rounded),
|
onPressed: () {
|
||||||
onPressed: () {
|
Clipboard.setData(
|
||||||
Clipboard.setData(
|
ClipboardData(
|
||||||
ClipboardData(
|
text: snapshot
|
||||||
text: snapshot
|
.data?.externalUrls?.spotify),
|
||||||
.data?.externalUrls?.spotify),
|
).then((val) {
|
||||||
).then((val) {
|
ScaffoldMessenger.of(context)
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
.showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
width: 300,
|
width: 300,
|
||||||
behavior: SnackBarBehavior.floating,
|
behavior: SnackBarBehavior.floating,
|
||||||
content: Text(
|
content: Text(
|
||||||
"Artist URL copied to clipboard",
|
"Artist URL copied to clipboard",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
});
|
},
|
||||||
},
|
)
|
||||||
)
|
],
|
||||||
],
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 50),
|
||||||
const SizedBox(height: 50),
|
FutureBuilder<Iterable<Track>>(
|
||||||
FutureBuilder<Iterable<Track>>(
|
future:
|
||||||
future:
|
spotify.artists.getTopTracks(snapshot.data!.id!, "US"),
|
||||||
spotify.artists.getTopTracks(snapshot.data!.id!, "US"),
|
builder: (context, trackSnapshot) {
|
||||||
builder: (context, trackSnapshot) {
|
if (!trackSnapshot.hasData) {
|
||||||
if (!trackSnapshot.hasData) {
|
return const Center(
|
||||||
return const Center(
|
child: CircularProgressIndicator.adaptive());
|
||||||
child: CircularProgressIndicator.adaptive());
|
}
|
||||||
}
|
Playback playback = ref.watch(playbackProvider);
|
||||||
Playback playback = ref.watch(playbackProvider);
|
var isPlaylistPlaying =
|
||||||
var isPlaylistPlaying =
|
playback.currentPlaylist?.id == snapshot.data?.id;
|
||||||
playback.currentPlaylist?.id == snapshot.data?.id;
|
playPlaylist(List<Track> tracks,
|
||||||
playPlaylist(List<Track> tracks,
|
{Track? currentTrack}) async {
|
||||||
{Track? currentTrack}) async {
|
currentTrack ??= tracks.first;
|
||||||
currentTrack ??= tracks.first;
|
if (!isPlaylistPlaying) {
|
||||||
if (!isPlaylistPlaying) {
|
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
tracks: tracks,
|
||||||
tracks: tracks,
|
id: snapshot.data!.id!,
|
||||||
id: snapshot.data!.id!,
|
name: "${snapshot.data!.name!} To Tracks",
|
||||||
name: "${snapshot.data!.name!} To Tracks",
|
thumbnail: imageToUrlString(snapshot.data?.images),
|
||||||
thumbnail: imageToUrlString(snapshot.data?.images),
|
);
|
||||||
);
|
playback.setCurrentTrack = currentTrack;
|
||||||
playback.setCurrentTrack = currentTrack;
|
} else if (isPlaylistPlaying &&
|
||||||
} else if (isPlaylistPlaying &&
|
currentTrack.id != null &&
|
||||||
currentTrack.id != null &&
|
currentTrack.id != playback.currentTrack?.id) {
|
||||||
currentTrack.id != playback.currentTrack?.id) {
|
playback.setCurrentTrack = currentTrack;
|
||||||
playback.setCurrentTrack = currentTrack;
|
}
|
||||||
|
await playback.startPlaying();
|
||||||
}
|
}
|
||||||
await playback.startPlaying();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Top Tracks",
|
"Top Tracks",
|
||||||
style: Theme.of(context).textTheme.headline4,
|
style: Theme.of(context).textTheme.headline4,
|
||||||
),
|
|
||||||
Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
),
|
||||||
child: IconButton(
|
Container(
|
||||||
icon: Icon(isPlaylistPlaying
|
margin: const EdgeInsets.symmetric(horizontal: 5),
|
||||||
? Icons.stop_rounded
|
decoration: BoxDecoration(
|
||||||
: Icons.play_arrow_rounded),
|
color: Theme.of(context).primaryColor,
|
||||||
color: Colors.white,
|
borderRadius: BorderRadius.circular(50),
|
||||||
onPressed: trackSnapshot.hasData
|
|
||||||
? () =>
|
|
||||||
playPlaylist(trackSnapshot.data!.toList())
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
...trackSnapshot.data
|
|
||||||
?.toList()
|
|
||||||
.asMap()
|
|
||||||
.entries
|
|
||||||
.map((track) {
|
|
||||||
String duration =
|
|
||||||
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
|
||||||
String? thumbnailUrl = imageToUrlString(
|
|
||||||
track.value.album?.images,
|
|
||||||
index:
|
|
||||||
(track.value.album?.images?.length ?? 1) -
|
|
||||||
1);
|
|
||||||
return TrackTile(
|
|
||||||
playback,
|
|
||||||
duration: duration,
|
|
||||||
track: track,
|
|
||||||
thumbnailUrl: thumbnailUrl,
|
|
||||||
onTrackPlayButtonPressed: (currentTrack) =>
|
|
||||||
playPlaylist(
|
|
||||||
trackSnapshot.data!.toList(),
|
|
||||||
currentTrack: track.value,
|
|
||||||
),
|
),
|
||||||
);
|
child: IconButton(
|
||||||
}) ??
|
icon: Icon(isPlaylistPlaying
|
||||||
[],
|
? Icons.stop_rounded
|
||||||
]);
|
: Icons.play_arrow_rounded),
|
||||||
},
|
color: Colors.white,
|
||||||
),
|
onPressed: trackSnapshot.hasData
|
||||||
const SizedBox(height: 50),
|
? () => playPlaylist(
|
||||||
Row(
|
trackSnapshot.data!.toList())
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
: null,
|
||||||
children: [
|
),
|
||||||
Text(
|
)
|
||||||
"Albums",
|
],
|
||||||
style: Theme.of(context).textTheme.headline4,
|
),
|
||||||
),
|
...trackSnapshot.data
|
||||||
TextButton(
|
?.toList()
|
||||||
child: const Text("See All"),
|
.asMap()
|
||||||
onPressed: () {
|
.entries
|
||||||
GoRouter.of(context).push(
|
.map((track) {
|
||||||
"/artist-album/$artistId",
|
String duration =
|
||||||
extra: snapshot.data?.name ?? "KRTX",
|
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||||
);
|
String? thumbnailUrl = imageToUrlString(
|
||||||
},
|
track.value.album?.images,
|
||||||
)
|
index:
|
||||||
],
|
(track.value.album?.images?.length ?? 1) -
|
||||||
),
|
1);
|
||||||
const SizedBox(height: 10),
|
return TrackTile(
|
||||||
FutureBuilder<List<Album>>(
|
playback,
|
||||||
future: spotify.artists
|
duration: duration,
|
||||||
.albums(snapshot.data!.id!)
|
track: track,
|
||||||
.getPage(5, 0)
|
thumbnailUrl: thumbnailUrl,
|
||||||
.then((al) => al.items?.toList() ?? []),
|
onTrackPlayButtonPressed: (currentTrack) =>
|
||||||
builder: (context, snapshot) {
|
playPlaylist(
|
||||||
if (!snapshot.hasData) {
|
trackSnapshot.data!.toList(),
|
||||||
return const Center(
|
currentTrack: track.value,
|
||||||
child: CircularProgressIndicator.adaptive());
|
),
|
||||||
}
|
);
|
||||||
return Scrollbar(
|
}) ??
|
||||||
controller: scrollController,
|
[],
|
||||||
child: SingleChildScrollView(
|
]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Albums",
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text("See All"),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).push(
|
||||||
|
"/artist-album/$artistId",
|
||||||
|
extra: snapshot.data?.name ?? "KRTX",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<List<Album>>(
|
||||||
|
future: spotify.artists
|
||||||
|
.albums(snapshot.data!.id!)
|
||||||
|
.getPage(5, 0)
|
||||||
|
.then((al) => al.items?.toList() ?? []),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
return Scrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
child: SingleChildScrollView(
|
||||||
child: Row(
|
controller: scrollController,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: snapshot.data
|
||||||
|
?.map((album) => AlbumCard(album))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
"Fans also likes",
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<Iterable<Artist>>(
|
||||||
|
future: spotify.artists.getRelatedArtists(artistId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
runSpacing: 20,
|
||||||
children: snapshot.data
|
children: snapshot.data
|
||||||
?.map((album) => AlbumCard(album))
|
?.map((artist) => ArtistCard(artist))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
)
|
||||||
),
|
],
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Text(
|
);
|
||||||
"Fans also likes",
|
},
|
||||||
style: Theme.of(context).textTheme.headline4,
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
FutureBuilder<Iterable<Artist>>(
|
|
||||||
future: spotify.artists.getRelatedArtists(artistId),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator.adaptive());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 20,
|
|
||||||
runSpacing: 20,
|
|
||||||
children: snapshot.data
|
|
||||||
?.map((artist) => ArtistCard(artist))
|
|
||||||
.toList() ??
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -152,65 +152,68 @@ class Home extends HookConsumerWidget {
|
|||||||
return const Login();
|
return const Login();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
body: Column(
|
child: Scaffold(
|
||||||
children: [
|
body: Column(
|
||||||
WindowTitleBarBox(
|
children: [
|
||||||
child: Row(
|
WindowTitleBarBox(
|
||||||
children: [
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxWidth: titleBarDragMaxWidth.toDouble(),
|
|
||||||
),
|
|
||||||
color:
|
|
||||||
Theme.of(context).navigationRailTheme.backgroundColor,
|
|
||||||
child: MoveWindow(),
|
|
||||||
),
|
|
||||||
Expanded(child: MoveWindow()),
|
|
||||||
if (!Platform.isMacOS) const TitleBarActionButtons(),
|
|
||||||
],
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Sidebar(
|
|
||||||
selectedIndex: _selectedIndex.value,
|
|
||||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
|
||||||
),
|
|
||||||
// contents of the spotify
|
|
||||||
if (_selectedIndex.value == 0)
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Row(
|
||||||
padding: const EdgeInsets.all(8.0),
|
children: [
|
||||||
child: PagedListView(
|
Container(
|
||||||
pagingController: pagingController,
|
constraints: BoxConstraints(
|
||||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
maxWidth: titleBarDragMaxWidth.toDouble(),
|
||||||
itemBuilder: (context, item, index) {
|
),
|
||||||
return CategoryCard(item);
|
color: Theme.of(context)
|
||||||
},
|
.navigationRailTheme
|
||||||
|
.backgroundColor,
|
||||||
|
child: MoveWindow(),
|
||||||
|
),
|
||||||
|
Expanded(child: MoveWindow()),
|
||||||
|
if (!Platform.isMacOS) const TitleBarActionButtons(),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Sidebar(
|
||||||
|
selectedIndex: _selectedIndex.value,
|
||||||
|
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||||
|
),
|
||||||
|
// contents of the spotify
|
||||||
|
if (_selectedIndex.value == 0)
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: PagedListView(
|
||||||
|
pagingController: pagingController,
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||||
|
itemBuilder: (context, item, index) {
|
||||||
|
return CategoryCard(item);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (_selectedIndex.value == 1) const Search(),
|
||||||
if (_selectedIndex.value == 1) const Search(),
|
if (_selectedIndex.value == 2) const UserLibrary(),
|
||||||
if (_selectedIndex.value == 2) const UserLibrary(),
|
if (_selectedIndex.value == 3) const Lyrics(),
|
||||||
if (_selectedIndex.value == 3) const Lyrics(),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
// player itself
|
||||||
// player itself
|
const Player(),
|
||||||
const Player(),
|
SpotubeNavigationBar(
|
||||||
SpotubeNavigationBar(
|
selectedIndex: _selectedIndex.value,
|
||||||
selectedIndex: _selectedIndex.value,
|
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,57 +42,59 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
[currentTrack?.album?.images],
|
[currentTrack?.album?.images],
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
appBar: const PageWindowTitleBar(
|
child: Scaffold(
|
||||||
leading: BackButton(),
|
appBar: const PageWindowTitleBar(
|
||||||
),
|
leading: BackButton(),
|
||||||
backgroundColor: paletteColor.color,
|
),
|
||||||
body: Column(
|
backgroundColor: paletteColor.color,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
body: Column(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(10),
|
Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.all(10),
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
children: [
|
||||||
currentTrack?.name ?? "Not playing",
|
Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
currentTrack?.name ?? "Not playing",
|
||||||
style: Theme.of(context).textTheme.headline4?.copyWith(
|
overflow: TextOverflow.ellipsis,
|
||||||
fontWeight: FontWeight.bold,
|
style: Theme.of(context).textTheme.headline4?.copyWith(
|
||||||
color: paletteColor.titleTextColor,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
color: paletteColor.titleTextColor,
|
||||||
),
|
),
|
||||||
artistsToClickableArtists(
|
),
|
||||||
currentTrack?.artists ?? [],
|
artistsToClickableArtists(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
currentTrack?.artists ?? [],
|
||||||
textStyle: Theme.of(context).textTheme.headline6!.copyWith(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
fontWeight: FontWeight.bold,
|
textStyle: Theme.of(context).textTheme.headline6!.copyWith(
|
||||||
color: paletteColor.bodyTextColor,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
color: paletteColor.bodyTextColor,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
HookBuilder(builder: (context) {
|
|
||||||
final ticker = useSingleTickerProvider();
|
|
||||||
final controller = useAnimationController(
|
|
||||||
duration: const Duration(seconds: 10),
|
|
||||||
vsync: ticker,
|
|
||||||
)..repeat();
|
|
||||||
return RotationTransition(
|
|
||||||
turns: Tween(begin: 0.0, end: 1.0).animate(controller),
|
|
||||||
child: CircleAvatar(
|
|
||||||
backgroundImage: CachedNetworkImageProvider(
|
|
||||||
albumArt,
|
|
||||||
cacheKey: albumArt,
|
|
||||||
),
|
|
||||||
radius: MediaQuery.of(context).size.width *
|
|
||||||
(breakpoint.isSm ? 0.4 : 0.3),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
HookBuilder(builder: (context) {
|
||||||
PlayerControls(iconColor: paletteColor.bodyTextColor),
|
final ticker = useSingleTickerProvider();
|
||||||
],
|
final controller = useAnimationController(
|
||||||
|
duration: const Duration(seconds: 10),
|
||||||
|
vsync: ticker,
|
||||||
|
)..repeat();
|
||||||
|
return RotationTransition(
|
||||||
|
turns: Tween(begin: 0.0, end: 1.0).animate(controller),
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
|
albumArt,
|
||||||
|
cacheKey: albumArt,
|
||||||
|
),
|
||||||
|
radius: MediaQuery.of(context).size.width *
|
||||||
|
(breakpoint.isSm ? 0.4 : 0.3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
PlayerControls(iconColor: paletteColor.bodyTextColor),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -38,64 +38,66 @@ class PlaylistView extends ConsumerWidget {
|
|||||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
||||||
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
||||||
playback.currentPlaylist?.id == playlist.id;
|
playback.currentPlaylist?.id == playlist.id;
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
body: FutureBuilder<Iterable<Track>>(
|
child: Scaffold(
|
||||||
future: playlist.id != "user-liked-tracks"
|
body: FutureBuilder<Iterable<Track>>(
|
||||||
? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all()
|
future: playlist.id != "user-liked-tracks"
|
||||||
: spotifyApi.tracks.me.saved
|
? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all()
|
||||||
.all()
|
: spotifyApi.tracks.me.saved
|
||||||
.then((tracks) => tracks.map((e) => e.track!)),
|
.all()
|
||||||
builder: (context, snapshot) {
|
.then((tracks) => tracks.map((e) => e.track!)),
|
||||||
List<Track> tracks = snapshot.data?.toList() ?? [];
|
builder: (context, snapshot) {
|
||||||
return Column(
|
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||||
children: [
|
return Column(
|
||||||
PageWindowTitleBar(
|
children: [
|
||||||
leading: Row(
|
PageWindowTitleBar(
|
||||||
children: [
|
leading: Row(
|
||||||
// nav back
|
children: [
|
||||||
const BackButton(),
|
// nav back
|
||||||
// heart playlist
|
const BackButton(),
|
||||||
IconButton(
|
// heart playlist
|
||||||
icon: const Icon(Icons.favorite_outline_rounded),
|
IconButton(
|
||||||
onPressed: () {},
|
icon: const Icon(Icons.favorite_outline_rounded),
|
||||||
),
|
onPressed: () {},
|
||||||
// play playlist
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
isPlaylistPlaying
|
|
||||||
? Icons.stop_rounded
|
|
||||||
: Icons.play_arrow_rounded,
|
|
||||||
),
|
),
|
||||||
onPressed: snapshot.hasData
|
// play playlist
|
||||||
? () => playPlaylist(playback, tracks)
|
IconButton(
|
||||||
: null,
|
icon: Icon(
|
||||||
)
|
isPlaylistPlaying
|
||||||
],
|
? Icons.stop_rounded
|
||||||
),
|
: Icons.play_arrow_rounded,
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: Text(playlist.name!,
|
|
||||||
style: Theme.of(context).textTheme.headline4),
|
|
||||||
),
|
|
||||||
snapshot.hasError
|
|
||||||
? const Center(child: Text("Error occurred"))
|
|
||||||
: !snapshot.hasData
|
|
||||||
? const Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator.adaptive()),
|
|
||||||
)
|
|
||||||
: TracksTableView(
|
|
||||||
tracks,
|
|
||||||
onTrackPlayButtonPressed: (currentTrack) =>
|
|
||||||
playPlaylist(
|
|
||||||
playback,
|
|
||||||
tracks,
|
|
||||||
currentTrack: currentTrack,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
onPressed: snapshot.hasData
|
||||||
);
|
? () => playPlaylist(playback, tracks)
|
||||||
}),
|
: null,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(playlist.name!,
|
||||||
|
style: Theme.of(context).textTheme.headline4),
|
||||||
|
),
|
||||||
|
snapshot.hasError
|
||||||
|
? const Center(child: Text("Error occurred"))
|
||||||
|
: !snapshot.hasData
|
||||||
|
? const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive()),
|
||||||
|
)
|
||||||
|
: TracksTableView(
|
||||||
|
tracks,
|
||||||
|
onTrackPlayButtonPressed: (currentTrack) =>
|
||||||
|
playPlaylist(
|
||||||
|
playback,
|
||||||
|
tracks,
|
||||||
|
currentTrack: currentTrack,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,172 +25,174 @@ class Settings extends HookConsumerWidget {
|
|||||||
geniusAccessToken.value = textEditingController.value.text;
|
geniusAccessToken.value = textEditingController.value.text;
|
||||||
});
|
});
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
appBar: PageWindowTitleBar(
|
child: Scaffold(
|
||||||
leading: const BackButton(),
|
appBar: PageWindowTitleBar(
|
||||||
center: Text(
|
leading: const BackButton(),
|
||||||
"Settings",
|
center: Text(
|
||||||
style: Theme.of(context).textTheme.headline5,
|
"Settings",
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
body: Padding(
|
||||||
body: Padding(
|
padding: const EdgeInsets.all(16.0),
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
flex: 2,
|
||||||
flex: 2,
|
child: Text(
|
||||||
child: Text(
|
"Genius Access Token",
|
||||||
"Genius Access Token",
|
style: Theme.of(context).textTheme.subtitle1,
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: TextField(
|
|
||||||
controller: textEditingController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: preferences.geniusAccessToken,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
Padding(
|
flex: 1,
|
||||||
padding: const EdgeInsets.all(8.0),
|
child: TextField(
|
||||||
child: ElevatedButton(
|
controller: textEditingController,
|
||||||
onPressed: geniusAccessToken.value != null
|
decoration: InputDecoration(
|
||||||
? () async {
|
hintText: preferences.geniusAccessToken,
|
||||||
SharedPreferences localStorage =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
preferences
|
|
||||||
.setGeniusAccessToken(geniusAccessToken.value);
|
|
||||||
localStorage.setString(
|
|
||||||
LocalStorageKeys.geniusAccessToken,
|
|
||||||
geniusAccessToken.value ?? "");
|
|
||||||
|
|
||||||
geniusAccessToken.value = null;
|
|
||||||
|
|
||||||
textEditingController.text = "";
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: const Text("Save"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
SettingsHotKeyTile(
|
|
||||||
title: "Next track global shortcut",
|
|
||||||
currentHotKey: preferences.nextTrackHotKey,
|
|
||||||
onHotKeyRecorded: (value) {
|
|
||||||
preferences.setNextTrackHotKey(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SettingsHotKeyTile(
|
|
||||||
title: "Prev track global shortcut",
|
|
||||||
currentHotKey: preferences.prevTrackHotKey,
|
|
||||||
onHotKeyRecorded: (value) {
|
|
||||||
preferences.setPrevTrackHotKey(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SettingsHotKeyTile(
|
|
||||||
title: "Play/Pause global shortcut",
|
|
||||||
currentHotKey: preferences.playPauseHotKey,
|
|
||||||
onHotKeyRecorded: (value) {
|
|
||||||
preferences.setPlayPauseHotKey(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text("Theme"),
|
|
||||||
DropdownButton<ThemeMode>(
|
|
||||||
value: theme,
|
|
||||||
items: const [
|
|
||||||
DropdownMenuItem(
|
|
||||||
child: Text(
|
|
||||||
"Dark",
|
|
||||||
),
|
),
|
||||||
value: ThemeMode.dark,
|
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
),
|
||||||
child: Text(
|
Padding(
|
||||||
"Light",
|
padding: const EdgeInsets.all(8.0),
|
||||||
),
|
child: ElevatedButton(
|
||||||
value: ThemeMode.light,
|
onPressed: geniusAccessToken.value != null
|
||||||
|
? () async {
|
||||||
|
SharedPreferences localStorage =
|
||||||
|
await SharedPreferences.getInstance();
|
||||||
|
preferences.setGeniusAccessToken(
|
||||||
|
geniusAccessToken.value);
|
||||||
|
localStorage.setString(
|
||||||
|
LocalStorageKeys.geniusAccessToken,
|
||||||
|
geniusAccessToken.value ?? "");
|
||||||
|
|
||||||
|
geniusAccessToken.value = null;
|
||||||
|
|
||||||
|
textEditingController.text = "";
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text("Save"),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
)
|
||||||
child: Text("System"),
|
],
|
||||||
value: ThemeMode.system,
|
),
|
||||||
),
|
const SizedBox(height: 10),
|
||||||
],
|
SettingsHotKeyTile(
|
||||||
onChanged: (value) {
|
title: "Next track global shortcut",
|
||||||
if (value != null) {
|
currentHotKey: preferences.nextTrackHotKey,
|
||||||
ref.read(themeProvider.notifier).state = value;
|
onHotKeyRecorded: (value) {
|
||||||
}
|
preferences.setNextTrackHotKey(value);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
SettingsHotKeyTile(
|
||||||
),
|
title: "Prev track global shortcut",
|
||||||
const SizedBox(height: 10),
|
currentHotKey: preferences.prevTrackHotKey,
|
||||||
Builder(builder: (context) {
|
onHotKeyRecorded: (value) {
|
||||||
Auth auth = ref.watch(authProvider);
|
preferences.setPrevTrackHotKey(value);
|
||||||
return Row(
|
},
|
||||||
|
),
|
||||||
|
SettingsHotKeyTile(
|
||||||
|
title: "Play/Pause global shortcut",
|
||||||
|
currentHotKey: preferences.playPauseHotKey,
|
||||||
|
onHotKeyRecorded: (value) {
|
||||||
|
preferences.setPlayPauseHotKey(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text("Log out of this account"),
|
const Text("Theme"),
|
||||||
ElevatedButton(
|
DropdownButton<ThemeMode>(
|
||||||
child: const Text("Logout"),
|
value: theme,
|
||||||
style: ButtonStyle(
|
items: const [
|
||||||
backgroundColor: MaterialStateProperty.all(Colors.red),
|
DropdownMenuItem(
|
||||||
),
|
child: Text(
|
||||||
onPressed: () async {
|
"Dark",
|
||||||
SharedPreferences localStorage =
|
),
|
||||||
await SharedPreferences.getInstance();
|
value: ThemeMode.dark,
|
||||||
await localStorage.clear();
|
),
|
||||||
auth.logout();
|
DropdownMenuItem(
|
||||||
GoRouter.of(context).pop();
|
child: Text(
|
||||||
|
"Light",
|
||||||
|
),
|
||||||
|
value: ThemeMode.light,
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
child: Text("System"),
|
||||||
|
value: ThemeMode.system,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref.read(themeProvider.notifier).state = value;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
Auth auth = ref.watch(authProvider);
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text("Log out of this account"),
|
||||||
|
ElevatedButton(
|
||||||
|
child: const Text("Logout"),
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(Colors.red),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
SharedPreferences localStorage =
|
||||||
|
await SharedPreferences.getInstance();
|
||||||
|
await localStorage.clear();
|
||||||
|
auth.logout();
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
const Text("Spotube v1.2.0"),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Text("Author: "),
|
||||||
|
Hyperlink(
|
||||||
|
"Kingkor Roy Tirtho",
|
||||||
|
"https://github.com/KRTirtho",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}),
|
const SizedBox(height: 20),
|
||||||
const SizedBox(height: 40),
|
Row(
|
||||||
const Text("Spotube v1.2.0"),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
const SizedBox(height: 10),
|
children: const [
|
||||||
Row(
|
Hyperlink(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
"💚 Sponsor/Donate 💚",
|
||||||
children: const [
|
"https://opencollective.com/spotube",
|
||||||
Text("Author: "),
|
),
|
||||||
Hyperlink(
|
Text(" • "),
|
||||||
"Kingkor Roy Tirtho",
|
Hyperlink(
|
||||||
"https://github.com/KRTirtho",
|
"BSD-4-Clause LICENSE",
|
||||||
),
|
"https://github.com/KRTirtho/spotube/blob/master/LICENSE",
|
||||||
],
|
),
|
||||||
),
|
Text(" • "),
|
||||||
const SizedBox(height: 20),
|
Hyperlink(
|
||||||
Row(
|
"Bug Report",
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
"https://github.com/KRTirtho/spotube/issues/new?assignees=&labels=bug&template=bug_report.md&title=",
|
||||||
children: const [
|
),
|
||||||
Hyperlink(
|
],
|
||||||
"💚 Sponsor/Donate 💚",
|
),
|
||||||
"https://opencollective.com/spotube",
|
const SizedBox(height: 10),
|
||||||
),
|
const Text("© Spotube 2022. All rights reserved")
|
||||||
Text(" • "),
|
],
|
||||||
Hyperlink(
|
),
|
||||||
"BSD-4-Clause LICENSE",
|
|
||||||
"https://github.com/KRTirtho/spotube/blob/master/LICENSE",
|
|
||||||
),
|
|
||||||
Text(" • "),
|
|
||||||
Hyperlink(
|
|
||||||
"Bug Report",
|
|
||||||
"https://github.com/KRTirtho/spotube/issues/new?assignees=&labels=bug&template=bug_report.md&title=",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
const Text("© Spotube 2022. All rights reserved")
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -52,10 +52,23 @@ class PageWindowTitleBar extends StatelessWidget
|
|||||||
const PageWindowTitleBar({Key? key, this.leading, this.center})
|
const PageWindowTitleBar({Key? key, this.leading, this.center})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => Size.fromHeight(appWindow.titleBarHeight);
|
Size get preferredSize => Size.fromHeight(
|
||||||
|
!Platform.isIOS && !Platform.isAndroid ? appWindow.titleBarHeight : 0,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (Platform.isIOS || Platform.isAndroid) {
|
||||||
|
return PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(70),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (leading != null) leading!,
|
||||||
|
Expanded(child: Center(child: center)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return WindowTitleBarBox(
|
return WindowTitleBarBox(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -28,7 +28,6 @@ Future<void> Function() usePreviousTrack(Playback playback) {
|
|||||||
|
|
||||||
Future<void> Function([dynamic]) useTogglePlayPause(Playback playback) {
|
Future<void> Function([dynamic]) useTogglePlayPause(Playback playback) {
|
||||||
return ([key]) async {
|
return ([key]) async {
|
||||||
print("CLICK CLICK");
|
|
||||||
try {
|
try {
|
||||||
if (playback.currentTrack == null) return;
|
if (playback.currentTrack == null) return;
|
||||||
playback.isPlaying
|
playback.isPlaying
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
@ -18,6 +20,7 @@ useHotKeys(WidgetRef ref) {
|
|||||||
final _playOrPause = useTogglePlayPause(playback);
|
final _playOrPause = useTogglePlayPause(playback);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
if (Platform.isIOS || Platform.isAndroid) return null;
|
||||||
_hotKeys = [
|
_hotKeys = [
|
||||||
GlobalKeyActions(
|
GlobalKeyActions(
|
||||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||||
|
@ -14,21 +14,25 @@ import 'package:spotube/provider/ThemeProvider.dart';
|
|||||||
import 'package:spotube/provider/YouTube.dart';
|
import 'package:spotube/provider/YouTube.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||||
await hotKeyManager.unregisterAll();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await hotKeyManager.unregisterAll();
|
||||||
|
doWhenWindowReady(() {
|
||||||
|
appWindow.minSize =
|
||||||
|
Size(Platform.isAndroid || Platform.isIOS ? 280 : 359, 700);
|
||||||
|
appWindow.size = const Size(900, 700);
|
||||||
|
appWindow.alignment = Alignment.center;
|
||||||
|
appWindow.maximize();
|
||||||
|
appWindow.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
runApp(ProviderScope(child: MyApp()));
|
runApp(ProviderScope(child: MyApp()));
|
||||||
doWhenWindowReady(() {
|
|
||||||
appWindow.minSize =
|
|
||||||
Size(Platform.isAndroid || Platform.isIOS ? 280 : 359, 700);
|
|
||||||
appWindow.size = const Size(900, 700);
|
|
||||||
appWindow.alignment = Alignment.center;
|
|
||||||
appWindow.maximize();
|
|
||||||
appWindow.show();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends HookConsumerWidget {
|
class MyApp extends HookConsumerWidget {
|
||||||
final GoRouter _router = createGoRouter();
|
final GoRouter _router = createGoRouter();
|
||||||
|
|
||||||
|
MyApp({Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
var themeMode = ref.watch(themeProvider);
|
var themeMode = ref.watch(themeProvider);
|
||||||
|
@ -5,7 +5,6 @@ import 'package:spotube/components/Album/AlbumView.dart';
|
|||||||
import 'package:spotube/components/Artist/ArtistAlbumView.dart';
|
import 'package:spotube/components/Artist/ArtistAlbumView.dart';
|
||||||
import 'package:spotube/components/Artist/ArtistProfile.dart';
|
import 'package:spotube/components/Artist/ArtistProfile.dart';
|
||||||
import 'package:spotube/components/Home/Home.dart';
|
import 'package:spotube/components/Home/Home.dart';
|
||||||
import 'package:spotube/components/Player/PlayerControls.dart';
|
|
||||||
import 'package:spotube/components/Player/PlayerView.dart';
|
import 'package:spotube/components/Player/PlayerView.dart';
|
||||||
import 'package:spotube/components/Playlist/PlaylistView.dart';
|
import 'package:spotube/components/Playlist/PlaylistView.dart';
|
||||||
import 'package:spotube/components/Settings.dart';
|
import 'package:spotube/components/Settings.dart';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:just_audio/just_audio.dart';
|
import 'package:just_audio/just_audio.dart';
|
||||||
@ -60,9 +61,11 @@ class Playback extends ChangeNotifier {
|
|||||||
StreamSubscription<bool>? _playingStreamListener;
|
StreamSubscription<bool>? _playingStreamListener;
|
||||||
StreamSubscription<Duration?>? _durationStreamListener;
|
StreamSubscription<Duration?>? _durationStreamListener;
|
||||||
StreamSubscription<ProcessingState>? _processingStateStreamListener;
|
StreamSubscription<ProcessingState>? _processingStateStreamListener;
|
||||||
|
StreamSubscription<AudioInterruptionEvent>? _audioInterruptionEventListener;
|
||||||
|
|
||||||
AudioPlayer player;
|
AudioPlayer player;
|
||||||
YoutubeExplode youtube;
|
YoutubeExplode youtube;
|
||||||
|
AudioSession? _audioSession;
|
||||||
Playback({
|
Playback({
|
||||||
required this.player,
|
required this.player,
|
||||||
required this.youtube,
|
required this.youtube,
|
||||||
@ -107,6 +110,14 @@ class Playback extends ChangeNotifier {
|
|||||||
print(stack);
|
print(stack);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AudioSession.instance.then((session) async {
|
||||||
|
_audioSession = session;
|
||||||
|
await session.configure(const AudioSessionConfiguration.music());
|
||||||
|
_audioInterruptionEventListener = session.interruptionEventStream.listen(
|
||||||
|
(AudioInterruptionEvent event) {},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentPlaylist? get currentPlaylist => _currentPlaylist;
|
CurrentPlaylist? get currentPlaylist => _currentPlaylist;
|
||||||
@ -175,6 +186,8 @@ class Playback extends ChangeNotifier {
|
|||||||
_processingStateStreamListener?.cancel();
|
_processingStateStreamListener?.cancel();
|
||||||
_durationStreamListener?.cancel();
|
_durationStreamListener?.cancel();
|
||||||
_playingStreamListener?.cancel();
|
_playingStreamListener?.cancel();
|
||||||
|
_audioInterruptionEventListener?.cancel();
|
||||||
|
_audioSession?.setActive(false);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +219,7 @@ class Playback extends ChangeNotifier {
|
|||||||
// the track is already playing so no need to change that
|
// the track is already playing so no need to change that
|
||||||
if (track != null && track.id == _currentTrack?.id) return;
|
if (track != null && track.id == _currentTrack?.id) return;
|
||||||
track ??= _currentTrack;
|
track ??= _currentTrack;
|
||||||
if (track != null) {
|
if (track != null && await _audioSession?.setActive(true) == true) {
|
||||||
Uri? parsedUri = Uri.tryParse(track.uri ?? "");
|
Uri? parsedUri = Uri.tryParse(track.uri ?? "");
|
||||||
if (parsedUri != null && parsedUri.hasAbsolutePath) {
|
if (parsedUri != null && parsedUri.hasAbsolutePath) {
|
||||||
await player
|
await player
|
||||||
|
@ -30,7 +30,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
version: "2.8.2"
|
||||||
audio_session:
|
audio_session:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: audio_session
|
name: audio_session
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
@ -323,7 +323,7 @@ packages:
|
|||||||
name: just_audio_web
|
name: just_audio_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.4"
|
version: "0.4.7"
|
||||||
libwinmedia:
|
libwinmedia:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -54,6 +54,7 @@ dependencies:
|
|||||||
hooks_riverpod: ^1.0.3
|
hooks_riverpod: ^1.0.3
|
||||||
go_router: ^3.0.4
|
go_router: ^3.0.4
|
||||||
palette_generator: ^0.3.3
|
palette_generator: ^0.3.3
|
||||||
|
audio_session: ^0.1.6+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user