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"
|
||||
package="oss.krtirtho.spotube">
|
||||
<application
|
||||
android:label="spotube"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<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
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<queries>
|
||||
<!-- If your app opens https URLs -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application android:label="spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true">
|
||||
<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
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.50'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -37,5 +37,11 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |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
|
||||
|
@ -1,47 +1,54 @@
|
||||
<?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">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Sptube</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>spotube</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Sptube</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>spotube</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true />
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true />
|
||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||
<true />
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -38,63 +38,65 @@ class AlbumView extends ConsumerWidget {
|
||||
|
||||
var isPlaylistPlaying = playback.currentPlaylist?.id == album.id;
|
||||
SpotifyApi spotify = ref.watch(spotifyProvider);
|
||||
return Scaffold(
|
||||
body: FutureBuilder<Iterable<TrackSimple>>(
|
||||
future: spotify.albums.getTracks(album.id!).all(),
|
||||
builder: (context, snapshot) {
|
||||
List<Track> tracks = snapshot.data?.map((trackSmp) {
|
||||
return simpleTrackToTrack(trackSmp, album);
|
||||
}).toList() ??
|
||||
[];
|
||||
return Column(
|
||||
children: [
|
||||
PageWindowTitleBar(
|
||||
leading: Row(
|
||||
children: [
|
||||
// nav back
|
||||
const BackButton(),
|
||||
// heart playlist
|
||||
IconButton(
|
||||
icon: const Icon(Icons.favorite_outline_rounded),
|
||||
onPressed: () {},
|
||||
),
|
||||
// play playlist
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: FutureBuilder<Iterable<TrackSimple>>(
|
||||
future: spotify.albums.getTracks(album.id!).all(),
|
||||
builder: (context, snapshot) {
|
||||
List<Track> tracks = snapshot.data?.map((trackSmp) {
|
||||
return simpleTrackToTrack(trackSmp, album);
|
||||
}).toList() ??
|
||||
[];
|
||||
return Column(
|
||||
children: [
|
||||
PageWindowTitleBar(
|
||||
leading: Row(
|
||||
children: [
|
||||
// nav back
|
||||
const BackButton(),
|
||||
// heart playlist
|
||||
IconButton(
|
||||
icon: const Icon(Icons.favorite_outline_rounded),
|
||||
onPressed: () {},
|
||||
),
|
||||
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,
|
||||
),
|
||||
// play playlist
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const PageWindowTitleBar(leading: BackButton()),
|
||||
body: Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.artistName,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
Expanded(
|
||||
child: PagedGridView(
|
||||
pagingController: _pagingController,
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 260,
|
||||
childAspectRatio: 9 / 13,
|
||||
crossAxisSpacing: 20,
|
||||
mainAxisSpacing: 20,
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return AlbumCard(item);
|
||||
},
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: const PageWindowTitleBar(leading: BackButton()),
|
||||
body: Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.artistName,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
Expanded(
|
||||
child: PagedGridView(
|
||||
pagingController: _pagingController,
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 260,
|
||||
childAspectRatio: 9 / 13,
|
||||
crossAxisSpacing: 20,
|
||||
mainAxisSpacing: 20,
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return AlbumCard(item);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -45,270 +45,273 @@ class ArtistProfile extends HookConsumerWidget {
|
||||
|
||||
final breakpoint = useBreakpoints();
|
||||
|
||||
return Scaffold(
|
||||
appBar: const PageWindowTitleBar(
|
||||
leading: BackButton(),
|
||||
),
|
||||
body: FutureBuilder<Artist>(
|
||||
future: spotify.artists.get(artistId),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: const PageWindowTitleBar(
|
||||
leading: BackButton(),
|
||||
),
|
||||
body: FutureBuilder<Artist>(
|
||||
future: spotify.artists.get(artistId),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
controller: parentScrollController,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 50),
|
||||
CircleAvatar(
|
||||
radius: avatarWidth,
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
imageToUrlString(snapshot.data!.images),
|
||||
return SingleChildScrollView(
|
||||
controller: parentScrollController,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 50),
|
||||
CircleAvatar(
|
||||
radius: avatarWidth,
|
||||
backgroundImage: CachedNetworkImageProvider(
|
||||
imageToUrlString(snapshot.data!.images),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Text(snapshot.data!.type!.toUpperCase(),
|
||||
style: chipTextVariant?.copyWith(
|
||||
color: Colors.white)),
|
||||
),
|
||||
Text(
|
||||
snapshot.data!.name!,
|
||||
style: breakpoint.isSm
|
||||
? textTheme.headline4
|
||||
: textTheme.headline2,
|
||||
),
|
||||
Text(
|
||||
"${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers",
|
||||
style: breakpoint.isSm
|
||||
? textTheme.bodyText1
|
||||
: textTheme.headline5,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// TODO: Implement check if user follows this artist
|
||||
// LIMITATION: spotify-dart lib
|
||||
FutureBuilder(
|
||||
future: Future.value(true),
|
||||
builder: (context, snapshot) {
|
||||
return OutlinedButton(
|
||||
onPressed: () async {
|
||||
// TODO: make `follow/unfollow` artists button work
|
||||
// LIMITATION: spotify-dart lib
|
||||
},
|
||||
child: Text(snapshot.data == true
|
||||
? "Following"
|
||||
: "Follow"),
|
||||
);
|
||||
}),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share_rounded),
|
||||
onPressed: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: snapshot
|
||||
.data?.externalUrls?.spotify),
|
||||
).then((val) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
width: 300,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(
|
||||
"Artist URL copied to clipboard",
|
||||
textAlign: TextAlign.center,
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue,
|
||||
borderRadius: BorderRadius.circular(50)),
|
||||
child: Text(snapshot.data!.type!.toUpperCase(),
|
||||
style: chipTextVariant?.copyWith(
|
||||
color: Colors.white)),
|
||||
),
|
||||
Text(
|
||||
snapshot.data!.name!,
|
||||
style: breakpoint.isSm
|
||||
? textTheme.headline4
|
||||
: textTheme.headline2,
|
||||
),
|
||||
Text(
|
||||
"${toReadableNumber(snapshot.data!.followers!.total!.toDouble())} followers",
|
||||
style: breakpoint.isSm
|
||||
? textTheme.bodyText1
|
||||
: textTheme.headline5,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// TODO: Implement check if user follows this artist
|
||||
// LIMITATION: spotify-dart lib
|
||||
FutureBuilder(
|
||||
future: Future.value(true),
|
||||
builder: (context, snapshot) {
|
||||
return OutlinedButton(
|
||||
onPressed: () async {
|
||||
// TODO: make `follow/unfollow` artists button work
|
||||
// LIMITATION: spotify-dart lib
|
||||
},
|
||||
child: Text(snapshot.data == true
|
||||
? "Following"
|
||||
: "Follow"),
|
||||
);
|
||||
}),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share_rounded),
|
||||
onPressed: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: snapshot
|
||||
.data?.externalUrls?.spotify),
|
||||
).then((val) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
const SnackBar(
|
||||
width: 300,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(
|
||||
"Artist URL copied to clipboard",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
FutureBuilder<Iterable<Track>>(
|
||||
future:
|
||||
spotify.artists.getTopTracks(snapshot.data!.id!, "US"),
|
||||
builder: (context, trackSnapshot) {
|
||||
if (!trackSnapshot.hasData) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
Playback playback = ref.watch(playbackProvider);
|
||||
var isPlaylistPlaying =
|
||||
playback.currentPlaylist?.id == snapshot.data?.id;
|
||||
playPlaylist(List<Track> tracks,
|
||||
{Track? currentTrack}) async {
|
||||
currentTrack ??= tracks.first;
|
||||
if (!isPlaylistPlaying) {
|
||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||
tracks: tracks,
|
||||
id: snapshot.data!.id!,
|
||||
name: "${snapshot.data!.name!} To Tracks",
|
||||
thumbnail: imageToUrlString(snapshot.data?.images),
|
||||
);
|
||||
playback.setCurrentTrack = currentTrack;
|
||||
} else if (isPlaylistPlaying &&
|
||||
currentTrack.id != null &&
|
||||
currentTrack.id != playback.currentTrack?.id) {
|
||||
playback.setCurrentTrack = currentTrack;
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
FutureBuilder<Iterable<Track>>(
|
||||
future:
|
||||
spotify.artists.getTopTracks(snapshot.data!.id!, "US"),
|
||||
builder: (context, trackSnapshot) {
|
||||
if (!trackSnapshot.hasData) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive());
|
||||
}
|
||||
Playback playback = ref.watch(playbackProvider);
|
||||
var isPlaylistPlaying =
|
||||
playback.currentPlaylist?.id == snapshot.data?.id;
|
||||
playPlaylist(List<Track> tracks,
|
||||
{Track? currentTrack}) async {
|
||||
currentTrack ??= tracks.first;
|
||||
if (!isPlaylistPlaying) {
|
||||
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||
tracks: tracks,
|
||||
id: snapshot.data!.id!,
|
||||
name: "${snapshot.data!.name!} To Tracks",
|
||||
thumbnail: imageToUrlString(snapshot.data?.images),
|
||||
);
|
||||
playback.setCurrentTrack = currentTrack;
|
||||
} else if (isPlaylistPlaying &&
|
||||
currentTrack.id != null &&
|
||||
currentTrack.id != playback.currentTrack?.id) {
|
||||
playback.setCurrentTrack = currentTrack;
|
||||
}
|
||||
await playback.startPlaying();
|
||||
}
|
||||
await playback.startPlaying();
|
||||
}
|
||||
|
||||
return Column(children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Top Tracks",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
return Column(children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Top Tracks",
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
child: IconButton(
|
||||
icon: Icon(isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded),
|
||||
color: Colors.white,
|
||||
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,
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
);
|
||||
}) ??
|
||||
[],
|
||||
]);
|
||||
},
|
||||
),
|
||||
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,
|
||||
child: SingleChildScrollView(
|
||||
child: IconButton(
|
||||
icon: Icon(isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded),
|
||||
color: Colors.white,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}) ??
|
||||
[],
|
||||
]);
|
||||
},
|
||||
),
|
||||
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,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
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
|
||||
?.map((album) => AlbumCard(album))
|
||||
?.map((artist) => ArtistCard(artist))
|
||||
.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 Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
WindowTitleBarBox(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
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)
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
WindowTitleBarBox(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PagedListView(
|
||||
pagingController: pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||
itemBuilder: (context, item, index) {
|
||||
return CategoryCard(item);
|
||||
},
|
||||
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(
|
||||
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 == 2) const UserLibrary(),
|
||||
if (_selectedIndex.value == 3) const Lyrics(),
|
||||
],
|
||||
if (_selectedIndex.value == 1) const Search(),
|
||||
if (_selectedIndex.value == 2) const UserLibrary(),
|
||||
if (_selectedIndex.value == 3) const Lyrics(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// player itself
|
||||
const Player(),
|
||||
SpotubeNavigationBar(
|
||||
selectedIndex: _selectedIndex.value,
|
||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||
),
|
||||
],
|
||||
// player itself
|
||||
const Player(),
|
||||
SpotubeNavigationBar(
|
||||
selectedIndex: _selectedIndex.value,
|
||||
onSelectedIndexChanged: _onSelectedIndexChanged,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -42,57 +42,59 @@ class PlayerView extends HookConsumerWidget {
|
||||
[currentTrack?.album?.images],
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: const PageWindowTitleBar(
|
||||
leading: BackButton(),
|
||||
),
|
||||
backgroundColor: paletteColor.color,
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
currentTrack?.name ?? "Not playing",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.headline4?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: paletteColor.titleTextColor,
|
||||
),
|
||||
),
|
||||
artistsToClickableArtists(
|
||||
currentTrack?.artists ?? [],
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
textStyle: Theme.of(context).textTheme.headline6!.copyWith(
|
||||
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),
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: const PageWindowTitleBar(
|
||||
leading: BackButton(),
|
||||
),
|
||||
backgroundColor: paletteColor.color,
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
currentTrack?.name ?? "Not playing",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.headline4?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: paletteColor.titleTextColor,
|
||||
),
|
||||
),
|
||||
artistsToClickableArtists(
|
||||
currentTrack?.artists ?? [],
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
textStyle: Theme.of(context).textTheme.headline6!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: paletteColor.bodyTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
PlayerControls(iconColor: 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),
|
||||
),
|
||||
);
|
||||
}),
|
||||
PlayerControls(iconColor: paletteColor.bodyTextColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -38,64 +38,66 @@ class PlaylistView extends ConsumerWidget {
|
||||
SpotifyApi spotifyApi = ref.watch(spotifyProvider);
|
||||
var isPlaylistPlaying = playback.currentPlaylist?.id != null &&
|
||||
playback.currentPlaylist?.id == playlist.id;
|
||||
return Scaffold(
|
||||
body: FutureBuilder<Iterable<Track>>(
|
||||
future: playlist.id != "user-liked-tracks"
|
||||
? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all()
|
||||
: spotifyApi.tracks.me.saved
|
||||
.all()
|
||||
.then((tracks) => tracks.map((e) => e.track!)),
|
||||
builder: (context, snapshot) {
|
||||
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||
return Column(
|
||||
children: [
|
||||
PageWindowTitleBar(
|
||||
leading: Row(
|
||||
children: [
|
||||
// nav back
|
||||
const BackButton(),
|
||||
// heart playlist
|
||||
IconButton(
|
||||
icon: const Icon(Icons.favorite_outline_rounded),
|
||||
onPressed: () {},
|
||||
),
|
||||
// play playlist
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
body: FutureBuilder<Iterable<Track>>(
|
||||
future: playlist.id != "user-liked-tracks"
|
||||
? spotifyApi.playlists.getTracksByPlaylistId(playlist.id).all()
|
||||
: spotifyApi.tracks.me.saved
|
||||
.all()
|
||||
.then((tracks) => tracks.map((e) => e.track!)),
|
||||
builder: (context, snapshot) {
|
||||
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||
return Column(
|
||||
children: [
|
||||
PageWindowTitleBar(
|
||||
leading: Row(
|
||||
children: [
|
||||
// nav back
|
||||
const BackButton(),
|
||||
// heart playlist
|
||||
IconButton(
|
||||
icon: const Icon(Icons.favorite_outline_rounded),
|
||||
onPressed: () {},
|
||||
),
|
||||
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,
|
||||
),
|
||||
// play playlist
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isPlaylistPlaying
|
||||
? Icons.stop_rounded
|
||||
: Icons.play_arrow_rounded,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
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;
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
leading: const BackButton(),
|
||||
center: Text(
|
||||
"Settings",
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
leading: const BackButton(),
|
||||
center: Text(
|
||||
"Settings",
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Genius Access Token",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
decoration: InputDecoration(
|
||||
hintText: preferences.geniusAccessToken,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Genius Access Token",
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ElevatedButton(
|
||||
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"),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
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",
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
decoration: InputDecoration(
|
||||
hintText: preferences.geniusAccessToken,
|
||||
),
|
||||
value: ThemeMode.dark,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
"Light",
|
||||
),
|
||||
value: ThemeMode.light,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ElevatedButton(
|
||||
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,
|
||||
),
|
||||
],
|
||||
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(
|
||||
)
|
||||
],
|
||||
),
|
||||
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("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 Text("Theme"),
|
||||
DropdownButton<ThemeMode>(
|
||||
value: theme,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
"Dark",
|
||||
),
|
||||
value: ThemeMode.dark,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
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: 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),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Hyperlink(
|
||||
"💚 Sponsor/Donate 💚",
|
||||
"https://opencollective.com/spotube",
|
||||
),
|
||||
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")
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Hyperlink(
|
||||
"💚 Sponsor/Donate 💚",
|
||||
"https://opencollective.com/spotube",
|
||||
),
|
||||
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})
|
||||
: super(key: key);
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(appWindow.titleBarHeight);
|
||||
Size get preferredSize => Size.fromHeight(
|
||||
!Platform.isIOS && !Platform.isAndroid ? appWindow.titleBarHeight : 0,
|
||||
);
|
||||
|
||||
@override
|
||||
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(
|
||||
child: Row(
|
||||
children: [
|
||||
|
@ -28,7 +28,6 @@ Future<void> Function() usePreviousTrack(Playback playback) {
|
||||
|
||||
Future<void> Function([dynamic]) useTogglePlayPause(Playback playback) {
|
||||
return ([key]) async {
|
||||
print("CLICK CLICK");
|
||||
try {
|
||||
if (playback.currentTrack == null) return;
|
||||
playback.isPlaying
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
@ -18,6 +20,7 @@ useHotKeys(WidgetRef ref) {
|
||||
final _playOrPause = useTogglePlayPause(playback);
|
||||
|
||||
useEffect(() {
|
||||
if (Platform.isIOS || Platform.isAndroid) return null;
|
||||
_hotKeys = [
|
||||
GlobalKeyActions(
|
||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||
|
@ -14,21 +14,25 @@ import 'package:spotube/provider/ThemeProvider.dart';
|
||||
import 'package:spotube/provider/YouTube.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await hotKeyManager.unregisterAll();
|
||||
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||
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()));
|
||||
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 {
|
||||
final GoRouter _router = createGoRouter();
|
||||
|
||||
MyApp({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
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/ArtistProfile.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/Playlist/PlaylistView.dart';
|
||||
import 'package:spotube/components/Settings.dart';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
@ -60,9 +61,11 @@ class Playback extends ChangeNotifier {
|
||||
StreamSubscription<bool>? _playingStreamListener;
|
||||
StreamSubscription<Duration?>? _durationStreamListener;
|
||||
StreamSubscription<ProcessingState>? _processingStateStreamListener;
|
||||
StreamSubscription<AudioInterruptionEvent>? _audioInterruptionEventListener;
|
||||
|
||||
AudioPlayer player;
|
||||
YoutubeExplode youtube;
|
||||
AudioSession? _audioSession;
|
||||
Playback({
|
||||
required this.player,
|
||||
required this.youtube,
|
||||
@ -107,6 +110,14 @@ class Playback extends ChangeNotifier {
|
||||
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;
|
||||
@ -175,6 +186,8 @@ class Playback extends ChangeNotifier {
|
||||
_processingStateStreamListener?.cancel();
|
||||
_durationStreamListener?.cancel();
|
||||
_playingStreamListener?.cancel();
|
||||
_audioInterruptionEventListener?.cancel();
|
||||
_audioSession?.setActive(false);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -206,7 +219,7 @@ class Playback extends ChangeNotifier {
|
||||
// the track is already playing so no need to change that
|
||||
if (track != null && track.id == _currentTrack?.id) return;
|
||||
track ??= _currentTrack;
|
||||
if (track != null) {
|
||||
if (track != null && await _audioSession?.setActive(true) == true) {
|
||||
Uri? parsedUri = Uri.tryParse(track.uri ?? "");
|
||||
if (parsedUri != null && parsedUri.hasAbsolutePath) {
|
||||
await player
|
||||
|
@ -30,7 +30,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.8.2"
|
||||
audio_session:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audio_session
|
||||
url: "https://pub.dartlang.org"
|
||||
@ -323,7 +323,7 @@ packages:
|
||||
name: just_audio_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.4"
|
||||
version: "0.4.7"
|
||||
libwinmedia:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -54,6 +54,7 @@ dependencies:
|
||||
hooks_riverpod: ^1.0.3
|
||||
go_router: ^3.0.4
|
||||
palette_generator: ^0.3.3
|
||||
audio_session: ^0.1.6+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
Reference in New Issue
Block a user