From c64f329c42926d6c1444bfe8f69ef319b95cf76a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 15 Mar 2022 19:47:29 +0600 Subject: [PATCH] Android SafeArea issues fixed configurations for different plugin for android added adjusted platform bound opertations --- android/app/src/main/AndroidManifest.xml | 56 +- android/build.gradle | 2 +- ios/Podfile | 6 + ios/Runner/Info.plist | 95 ++-- lib/components/Album/AlbumView.dart | 112 ++-- lib/components/Artist/ArtistAlbumView.dart | 50 +- lib/components/Artist/ArtistProfile.dart | 505 +++++++++--------- lib/components/Home/Home.dart | 111 ++-- lib/components/Player/PlayerView.dart | 102 ++-- lib/components/Playlist/PlaylistView.dart | 114 ++-- lib/components/Settings.dart | 312 +++++------ lib/components/Shared/PageWindowTitleBar.dart | 15 +- lib/hooks/playback.dart | 1 - lib/hooks/useHotKeys.dart | 3 + lib/main.dart | 24 +- lib/models/GoRouteDeclarations.dart | 1 - lib/provider/Playback.dart | 15 +- pubspec.lock | 4 +- pubspec.yaml | 1 + 19 files changed, 793 insertions(+), 736 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 19246de8..591455ab 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,34 +1,30 @@ - - - - + + + + + + + + + - - - - - - - - - - + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 24047dce..4256f917 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() diff --git a/ios/Podfile b/ios/Podfile index 1e8c3c90..42542813 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -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 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 474ff9f7..05d639bc 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,47 +1,54 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Sptube - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - spotube - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Sptube + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + spotube + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + + + \ No newline at end of file diff --git a/lib/components/Album/AlbumView.dart b/lib/components/Album/AlbumView.dart index 2d1f0da8..2054602d 100644 --- a/lib/components/Album/AlbumView.dart +++ b/lib/components/Album/AlbumView.dart @@ -38,63 +38,65 @@ class AlbumView extends ConsumerWidget { var isPlaylistPlaying = playback.currentPlaylist?.id == album.id; SpotifyApi spotify = ref.watch(spotifyProvider); - return Scaffold( - body: FutureBuilder>( - future: spotify.albums.getTracks(album.id!).all(), - builder: (context, snapshot) { - List 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>( + future: spotify.albums.getTracks(album.id!).all(), + builder: (context, snapshot) { + List 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, + ), + ), + ], + ); + }), + ), ); } } diff --git a/lib/components/Artist/ArtistAlbumView.dart b/lib/components/Artist/ArtistAlbumView.dart index fb3c150b..b90a78a6 100644 --- a/lib/components/Artist/ArtistAlbumView.dart +++ b/lib/components/Artist/ArtistAlbumView.dart @@ -59,32 +59,34 @@ class _ArtistAlbumViewState extends ConsumerState { @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( - 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( + itemBuilder: (context, item, index) { + return AlbumCard(item); + }, + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/components/Artist/ArtistProfile.dart b/lib/components/Artist/ArtistProfile.dart index 2ae74acb..f5f47f65 100644 --- a/lib/components/Artist/ArtistProfile.dart +++ b/lib/components/Artist/ArtistProfile.dart @@ -45,270 +45,273 @@ class ArtistProfile extends HookConsumerWidget { final breakpoint = useBreakpoints(); - return Scaffold( - appBar: const PageWindowTitleBar( - leading: BackButton(), - ), - body: FutureBuilder( - 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( + 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>( - 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 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>( + 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 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>( - 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>( + 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>( + 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>( - 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() ?? - [], - ), - ); - }, - ) - ], - ), - ); - }, + ); + }, + ) + ], + ), + ); + }, + ), ), ); } diff --git a/lib/components/Home/Home.dart b/lib/components/Home/Home.dart index 449a5bc7..10fbec93 100644 --- a/lib/components/Home/Home.dart +++ b/lib/components/Home/Home.dart @@ -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( - 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( + 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, + ), + ], + ), ), ); } diff --git a/lib/components/Player/PlayerView.dart b/lib/components/Player/PlayerView.dart index c6281970..c410d744 100644 --- a/lib/components/Player/PlayerView.dart +++ b/lib/components/Player/PlayerView.dart @@ -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), + ], + ), ), ); } diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 1c8ef708..fefced39 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -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>( - 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 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>( + 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 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, + ), + ), + ], + ); + }), + ), ); } } diff --git a/lib/components/Settings.dart b/lib/components/Settings.dart index 32060030..117a8037 100644 --- a/lib/components/Settings.dart +++ b/lib/components/Settings.dart @@ -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( - 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( + 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") + ], + ), ), ), ); diff --git a/lib/components/Shared/PageWindowTitleBar.dart b/lib/components/Shared/PageWindowTitleBar.dart index 483cc4a4..02cc76f5 100644 --- a/lib/components/Shared/PageWindowTitleBar.dart +++ b/lib/components/Shared/PageWindowTitleBar.dart @@ -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: [ diff --git a/lib/hooks/playback.dart b/lib/hooks/playback.dart index 5fe1bdce..f113d315 100644 --- a/lib/hooks/playback.dart +++ b/lib/hooks/playback.dart @@ -28,7 +28,6 @@ Future Function() usePreviousTrack(Playback playback) { Future Function([dynamic]) useTogglePlayPause(Playback playback) { return ([key]) async { - print("CLICK CLICK"); try { if (playback.currentTrack == null) return; playback.isPlaying diff --git a/lib/hooks/useHotKeys.dart b/lib/hooks/useHotKeys.dart index 2ccfb349..94ad8f5a 100644 --- a/lib/hooks/useHotKeys.dart +++ b/lib/hooks/useHotKeys.dart @@ -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), diff --git a/lib/main.dart b/lib/main.dart index a3a3c56c..25918053 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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); diff --git a/lib/models/GoRouteDeclarations.dart b/lib/models/GoRouteDeclarations.dart index 926c2213..3a871a10 100644 --- a/lib/models/GoRouteDeclarations.dart +++ b/lib/models/GoRouteDeclarations.dart @@ -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'; diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index 4d59d981..f87a0046 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.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? _playingStreamListener; StreamSubscription? _durationStreamListener; StreamSubscription? _processingStateStreamListener; + StreamSubscription? _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 diff --git a/pubspec.lock b/pubspec.lock index 0d670b24..5a3d5be2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index cfcedea6..87dc4d5f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: