mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
Playlist info editing (#708)
* feat: playlist metadata edit support * refactor: replace file_picker with file_selector
This commit is contained in:
parent
0df8d9cace
commit
ab0fe5bdfa
@ -23,7 +23,7 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@ -42,17 +42,23 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||||
<true/>
|
<true />
|
||||||
</dict>
|
</dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false/>
|
<false />
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>This app require access to the photo library</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>This app require access to the device camera</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>This app does not require access to the device microphone</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
@ -94,4 +94,6 @@ abstract class SpotubeIcons {
|
|||||||
static const noWifi = FeatherIcons.wifiOff;
|
static const noWifi = FeatherIcons.wifiOff;
|
||||||
static const wifi = FeatherIcons.wifi;
|
static const wifi = FeatherIcons.wifi;
|
||||||
static const window = Icons.window_rounded;
|
static const window = Icons.window_rounded;
|
||||||
|
static const user = FeatherIcons.user;
|
||||||
|
static const edit = FeatherIcons.edit;
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
final updating = useState(false);
|
final updating = useState(false);
|
||||||
final spotify = ref.watch(spotifyProvider);
|
final spotify = ref.watch(spotifyProvider);
|
||||||
|
final me = useQueries.user.me(ref);
|
||||||
|
|
||||||
return PlaybuttonCard(
|
return PlaybuttonCard(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
@ -44,6 +45,7 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
isPlaying: isPlaylistPlaying,
|
isPlaying: isPlaylistPlaying,
|
||||||
isLoading:
|
isLoading:
|
||||||
(isPlaylistPlaying && playlistQueue.isFetching) || updating.value,
|
(isPlaylistPlaying && playlistQueue.isFetching) || updating.value,
|
||||||
|
isOwner: playlist.owner?.id == me.data?.id && me.data?.id != null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
ServiceUtils.push(
|
ServiceUtils.push(
|
||||||
context,
|
context,
|
||||||
|
@ -1,58 +1,134 @@
|
|||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:form_validator/form_validator.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
|
import 'package:spotube/services/mutations/mutations.dart';
|
||||||
|
import 'package:spotube/services/mutations/playlist.dart';
|
||||||
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||||
|
|
||||||
class PlaylistCreateDialog extends HookConsumerWidget {
|
class PlaylistCreateDialog extends HookConsumerWidget {
|
||||||
/// Track ids to add to the playlist
|
/// Track ids to add to the playlist
|
||||||
final List<String> trackIds;
|
final List<String> trackIds;
|
||||||
const PlaylistCreateDialog({
|
final String? playlistId;
|
||||||
|
PlaylistCreateDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.trackIds = const [],
|
this.trackIds = const [],
|
||||||
|
this.playlistId,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final spotify = ref.watch(spotifyProvider);
|
return ScaffoldMessenger(
|
||||||
final playlistName = useTextEditingController();
|
child: Scaffold(
|
||||||
final description = useTextEditingController();
|
backgroundColor: Colors.transparent,
|
||||||
final public = useState(false);
|
body: HookBuilder(builder: (context) {
|
||||||
final collaborative = useState(false);
|
final userPlaylists = useQueries.playlist.ofMine(ref);
|
||||||
final client = useQueryClient();
|
final updatingPlaylist = useMemoized(
|
||||||
final navigator = Navigator.of(context);
|
() => userPlaylists.pages
|
||||||
|
.expand((p) => p.items ?? <PlaylistSimple>[])
|
||||||
|
.firstWhereOrNull((playlist) => playlist.id == playlistId),
|
||||||
|
[
|
||||||
|
userPlaylists.pages,
|
||||||
|
playlistId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final playlistName = useTextEditingController(
|
||||||
|
text: updatingPlaylist?.name,
|
||||||
|
);
|
||||||
|
final description = useTextEditingController(
|
||||||
|
text: updatingPlaylist?.description,
|
||||||
|
);
|
||||||
|
final public = useState(
|
||||||
|
updatingPlaylist?.public ?? false,
|
||||||
|
);
|
||||||
|
final collaborative = useState(
|
||||||
|
updatingPlaylist?.collaborative ?? false,
|
||||||
|
);
|
||||||
|
final image = useState<XFile?>(null);
|
||||||
|
|
||||||
|
final isUpdatingPlaylist = playlistId != null;
|
||||||
|
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scaffold = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
|
final onError = useCallback((error) {
|
||||||
|
if (error is SpotifyError || error is SpotifyException) {
|
||||||
|
scaffold.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
l10n.error(error.message ?? "Epic failure!"),
|
||||||
|
style: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: theme.colorScheme.onError,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [scaffold, l10n, theme]);
|
||||||
|
|
||||||
|
final playlistCreateMutation = useMutations.playlist.create(
|
||||||
|
ref,
|
||||||
|
trackIds: trackIds,
|
||||||
|
onData: (value) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
onError: onError,
|
||||||
|
);
|
||||||
|
|
||||||
|
final playlistUpdateMutation = useMutations.playlist.update(
|
||||||
|
ref,
|
||||||
|
playlistId: playlistId,
|
||||||
|
onData: (value) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
onError: onError,
|
||||||
|
);
|
||||||
|
|
||||||
Future<void> onCreate() async {
|
Future<void> onCreate() async {
|
||||||
if (playlistName.text.isEmpty) return;
|
if (!formKey.currentState!.validate()) return;
|
||||||
final me = await spotify.me.get();
|
|
||||||
final playlist = await spotify.playlists.createPlaylist(
|
final PlaylistCRUDVariables payload = (
|
||||||
me.id!,
|
playlistName: playlistName.text,
|
||||||
playlistName.text,
|
|
||||||
collaborative: collaborative.value,
|
collaborative: collaborative.value,
|
||||||
public: public.value,
|
public: public.value,
|
||||||
description: description.text,
|
description: description.text,
|
||||||
|
base64Image: image.value?.path != null
|
||||||
|
? await image.value!
|
||||||
|
.readAsBytes()
|
||||||
|
.then((bytes) => base64Encode(bytes))
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
if (trackIds.isNotEmpty) {
|
|
||||||
await spotify.playlists.addTracks(
|
if (isUpdatingPlaylist) {
|
||||||
trackIds.map((id) => "spotify:track:$id").toList(),
|
await playlistUpdateMutation.mutate(payload);
|
||||||
playlist.id!,
|
} else {
|
||||||
);
|
await playlistCreateMutation.mutate(payload);
|
||||||
}
|
}
|
||||||
await client
|
|
||||||
.getQuery(
|
|
||||||
"current-user-playlists",
|
|
||||||
)
|
|
||||||
?.refresh();
|
|
||||||
navigator.pop(playlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(context.l10n.create_a_playlist),
|
title: Text(
|
||||||
|
isUpdatingPlaylist
|
||||||
|
? context.l10n.update_playlist
|
||||||
|
: context.l10n.create_a_playlist,
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
child: Text(context.l10n.cancel),
|
child: Text(context.l10n.cancel),
|
||||||
@ -62,24 +138,66 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: onCreate,
|
onPressed: onCreate,
|
||||||
child: Text(context.l10n.create),
|
child: Text(
|
||||||
|
isUpdatingPlaylist
|
||||||
|
? context.l10n.update
|
||||||
|
: context.l10n.create,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
insetPadding: const EdgeInsets.all(8),
|
||||||
content: Container(
|
content: Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: const BoxConstraints(maxWidth: 500),
|
||||||
|
child: Form(
|
||||||
|
key: formKey,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
Center(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
UniversalImage(
|
||||||
|
path: image.value?.path ??
|
||||||
|
TypeConversionUtils.image_X_UrlString(
|
||||||
|
updatingPlaylist?.images,
|
||||||
|
placeholder: ImagePlaceholder.collection,
|
||||||
|
),
|
||||||
|
height: 200,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 20,
|
||||||
|
right: 20,
|
||||||
|
child: IconButton.filled(
|
||||||
|
icon: const Icon(SpotubeIcons.edit),
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: theme.colorScheme.surface,
|
||||||
|
foregroundColor: theme.colorScheme.primary,
|
||||||
|
elevation: 2,
|
||||||
|
shadowColor: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final imageFile = await ImagePicker()
|
||||||
|
.pickImage(source: ImageSource.gallery);
|
||||||
|
|
||||||
|
image.value = imageFile ?? image.value;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextFormField(
|
||||||
controller: playlistName,
|
controller: playlistName,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: context.l10n.name_of_playlist,
|
hintText: context.l10n.name_of_playlist,
|
||||||
labelText: context.l10n.name_of_playlist,
|
labelText: context.l10n.name_of_playlist,
|
||||||
),
|
),
|
||||||
|
validator: ValidationBuilder().required().build(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextFormField(
|
||||||
controller: description,
|
controller: description,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: context.l10n.description,
|
hintText: context.l10n.description,
|
||||||
@ -102,6 +220,10 @@ class PlaylistCreateDialog extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +234,7 @@ class PlaylistCreateDialogButton extends HookConsumerWidget {
|
|||||||
showPlaylistDialog(BuildContext context, SpotifyApi spotify) {
|
showPlaylistDialog(BuildContext context, SpotifyApi spotify) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const PlaylistCreateDialog(),
|
builder: (context) => PlaylistCreateDialog(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +259,7 @@ class PlaylistCreateDialogButton extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
icon: const Icon(SpotubeIcons.addFilled),
|
icon: const Icon(SpotubeIcons.addFilled),
|
||||||
label: Text(context.l10n.create_playlist),
|
label: Text(context.l10n.create_playlist),
|
||||||
onPressed: () => showPlaylistDialog(context, spotify));
|
onPressed: () => showPlaylistDialog(context, spotify),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,10 +159,14 @@ class TrackHeartButton extends HookConsumerWidget {
|
|||||||
|
|
||||||
class PlaylistHeartButton extends HookConsumerWidget {
|
class PlaylistHeartButton extends HookConsumerWidget {
|
||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlist;
|
||||||
|
final IconData? icon;
|
||||||
|
final ValueChanged<bool>? onData;
|
||||||
|
|
||||||
const PlaylistHeartButton({
|
const PlaylistHeartButton({
|
||||||
required this.playlist,
|
required this.playlist,
|
||||||
Key? key,
|
Key? key,
|
||||||
|
this.icon,
|
||||||
|
this.onData,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -181,6 +185,7 @@ class PlaylistHeartButton extends HookConsumerWidget {
|
|||||||
refreshQueries: [
|
refreshQueries: [
|
||||||
isLikedQuery.key,
|
isLikedQuery.key,
|
||||||
],
|
],
|
||||||
|
onData: onData,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (me.isLoading || !me.hasData) {
|
if (me.isLoading || !me.hasData) {
|
||||||
@ -193,6 +198,7 @@ class PlaylistHeartButton extends HookConsumerWidget {
|
|||||||
? context.l10n.remove_from_favorites
|
? context.l10n.remove_from_favorites
|
||||||
: context.l10n.save_as_favorite,
|
: context.l10n.save_as_favorite,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
icon: icon,
|
||||||
onPressed: isLikedQuery.hasData
|
onPressed: isLikedQuery.hasData
|
||||||
? () {
|
? () {
|
||||||
togglePlaylistLike.mutate(isLikedQuery.data!);
|
togglePlaylistLike.mutate(isLikedQuery.data!);
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/shared/hover_builder.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/hooks/use_brightness_value.dart';
|
import 'package:spotube/hooks/use_brightness_value.dart';
|
||||||
@ -28,6 +29,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
final bool isPlaying;
|
final bool isPlaying;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String title;
|
final String title;
|
||||||
|
final bool isOwner;
|
||||||
|
|
||||||
const PlaybuttonCard({
|
const PlaybuttonCard({
|
||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
@ -39,6 +41,7 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
this.onPlaybuttonPressed,
|
this.onPlaybuttonPressed,
|
||||||
this.onAddToQueuePressed,
|
this.onAddToQueuePressed,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.isOwner = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -153,6 +156,42 @@ class PlaybuttonCard extends HookWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (isOwner)
|
||||||
|
Positioned(
|
||||||
|
top: 15,
|
||||||
|
left: 25,
|
||||||
|
child: AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
curve: Curves.easeInExpo,
|
||||||
|
child: HoverBuilder(builder: (context, isHovered) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
SpotubeIcons.user,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
if (isHovered)
|
||||||
|
Text(
|
||||||
|
"Owned by you",
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
AnimatedPositioned(
|
AnimatedPositioned(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
right: end,
|
right: end,
|
||||||
|
@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
||||||
@ -71,6 +72,18 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
icon: const Icon(SpotubeIcons.share),
|
icon: const Icon(SpotubeIcons.share),
|
||||||
onPressed: onShare,
|
onPressed: onShare,
|
||||||
),
|
),
|
||||||
|
if (isOwned)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(SpotubeIcons.edit),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return PlaylistCreateDialog(playlistId: id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (heartBtn != null && auth != null) heartBtn!,
|
if (heartBtn != null && auth != null) heartBtn!,
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: playingState == PlayButtonState.playing
|
onPressed: playingState == PlayButtonState.playing
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import 'package:audio_service_web/audio_service_web.dart';
|
import 'package:audio_service_web/audio_service_web.dart';
|
||||||
import 'package:audio_session/audio_session_web.dart';
|
import 'package:audio_session/audio_session_web.dart';
|
||||||
import 'package:file_picker/_internal/file_picker_web.dart';
|
|
||||||
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
||||||
import 'package:url_launcher_web/url_launcher_web.dart';
|
import 'package:url_launcher_web/url_launcher_web.dart';
|
||||||
|
|
||||||
@ -18,7 +17,6 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
|||||||
void registerPlugins(Registrar registrar) {
|
void registerPlugins(Registrar registrar) {
|
||||||
AudioServiceWeb.registerWith(registrar);
|
AudioServiceWeb.registerWith(registrar);
|
||||||
AudioSessionWeb.registerWith(registrar);
|
AudioSessionWeb.registerWith(registrar);
|
||||||
FilePickerWeb.registerWith(registrar);
|
|
||||||
SharedPreferencesPlugin.registerWith(registrar);
|
SharedPreferencesPlugin.registerWith(registrar);
|
||||||
UrlLauncherPlugin.registerWith(registrar);
|
UrlLauncherPlugin.registerWith(registrar);
|
||||||
registrar.registerMessageHandler();
|
registrar.registerMessageHandler();
|
||||||
|
@ -24,8 +24,10 @@
|
|||||||
"liked_tracks_description": "All your liked tracks",
|
"liked_tracks_description": "All your liked tracks",
|
||||||
"create_playlist": "Create Playlist",
|
"create_playlist": "Create Playlist",
|
||||||
"create_a_playlist": "Create a playlist",
|
"create_a_playlist": "Create a playlist",
|
||||||
|
"update_playlist": "Update playlist",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"update": "update",
|
||||||
"playlist_name": "Playlist Name",
|
"playlist_name": "Playlist Name",
|
||||||
"name_of_playlist": "Name of the playlist",
|
"name_of_playlist": "Name of the playlist",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/shared/heart_button.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_view.dart';
|
||||||
@ -18,34 +20,8 @@ import 'package:spotube/utils/type_conversion_utils.dart';
|
|||||||
|
|
||||||
class PlaylistView extends HookConsumerWidget {
|
class PlaylistView extends HookConsumerWidget {
|
||||||
final logger = getLogger(PlaylistView);
|
final logger = getLogger(PlaylistView);
|
||||||
final PlaylistSimple playlist;
|
final PlaylistSimple playlistSimple;
|
||||||
PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
PlaylistView(this.playlistSimple, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
Future<void> playPlaylist(
|
|
||||||
List<Track> tracks,
|
|
||||||
WidgetRef ref, {
|
|
||||||
Track? currentTrack,
|
|
||||||
}) async {
|
|
||||||
final proxyPlaylist = ref.read(ProxyPlaylistNotifier.provider);
|
|
||||||
final playback = ref.read(ProxyPlaylistNotifier.notifier);
|
|
||||||
final sortBy = ref.read(trackCollectionSortState(playlist.id!));
|
|
||||||
final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy);
|
|
||||||
currentTrack ??= sortedTracks.first;
|
|
||||||
final isPlaylistPlaying = proxyPlaylist.containsTracks(tracks);
|
|
||||||
if (!isPlaylistPlaying) {
|
|
||||||
playback.addCollection(playlist.id!); // for enabling loading indicator
|
|
||||||
await playback.load(
|
|
||||||
sortedTracks,
|
|
||||||
initialIndex: sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
|
|
||||||
autoPlay: true,
|
|
||||||
);
|
|
||||||
playback.addCollection(playlist.id!);
|
|
||||||
} else if (isPlaylistPlaying &&
|
|
||||||
currentTrack.id != null &&
|
|
||||||
currentTrack.id != proxyPlaylist.activeTrack?.id) {
|
|
||||||
await playback.jumpToTrack(currentTrack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
@ -55,6 +31,10 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
final meSnapshot = useQueries.user.me(ref);
|
final meSnapshot = useQueries.user.me(ref);
|
||||||
|
|
||||||
|
final playlistQuery = useQueries.playlist.byId(ref, playlistSimple.id!);
|
||||||
|
final playlist = playlistQuery.data ?? playlistSimple;
|
||||||
|
|
||||||
final playlistTrackSnapshot =
|
final playlistTrackSnapshot =
|
||||||
useQueries.playlist.tracksOfQuery(ref, playlist.id!);
|
useQueries.playlist.tracksOfQuery(ref, playlist.id!);
|
||||||
final likedTracksSnapshot = useQueries.playlist.likedTracksQuery(ref);
|
final likedTracksSnapshot = useQueries.playlist.likedTracksQuery(ref);
|
||||||
@ -83,6 +63,35 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
[proxyPlaylist.activeTrack, tracksSnapshot.data],
|
[proxyPlaylist.activeTrack, tracksSnapshot.data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final playPlaylist = useCallback((
|
||||||
|
List<Track> tracks,
|
||||||
|
WidgetRef ref, {
|
||||||
|
Track? currentTrack,
|
||||||
|
}) async {
|
||||||
|
final playback = ref.read(ProxyPlaylistNotifier.notifier);
|
||||||
|
final sortBy = ref.read(trackCollectionSortState(playlist.id!));
|
||||||
|
final sortedTracks = ServiceUtils.sortTracks(tracks, sortBy);
|
||||||
|
currentTrack ??= sortedTracks.first;
|
||||||
|
final isPlaylistPlaying = proxyPlaylist.containsTracks(tracks);
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
playback.addCollection(playlist.id!); // for enabling loading indicator
|
||||||
|
await playback.load(
|
||||||
|
sortedTracks,
|
||||||
|
initialIndex:
|
||||||
|
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
|
autoPlay: true,
|
||||||
|
);
|
||||||
|
playback.addCollection(playlist.id!);
|
||||||
|
} else if (isPlaylistPlaying &&
|
||||||
|
currentTrack.id != null &&
|
||||||
|
currentTrack.id != proxyPlaylist.activeTrack?.id) {
|
||||||
|
await playback.jumpToTrack(currentTrack);
|
||||||
|
}
|
||||||
|
}, [proxyPlaylist, playlist]);
|
||||||
|
|
||||||
|
final ownPlaylist =
|
||||||
|
playlist.owner?.id != null && playlist.owner?.id == meSnapshot.data?.id;
|
||||||
|
|
||||||
return TrackCollectionView(
|
return TrackCollectionView(
|
||||||
id: playlist.id!,
|
id: playlist.id!,
|
||||||
playingState: isPlaylistPlaying && playlistTrackPlaying
|
playingState: isPlaylistPlaying && playlistTrackPlaying
|
||||||
@ -94,8 +103,7 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
titleImage: titleImage,
|
titleImage: titleImage,
|
||||||
tracksSnapshot: tracksSnapshot,
|
tracksSnapshot: tracksSnapshot,
|
||||||
description: playlist.description,
|
description: playlist.description,
|
||||||
isOwned: playlist.owner?.id != null &&
|
isOwned: ownPlaylist,
|
||||||
playlist.owner!.id == meSnapshot.data?.id,
|
|
||||||
onPlay: ([track]) {
|
onPlay: ([track]) {
|
||||||
if (tracksSnapshot.hasData) {
|
if (tracksSnapshot.hasData) {
|
||||||
if (!isPlaylistPlaying) {
|
if (!isPlaylistPlaying) {
|
||||||
@ -142,7 +150,13 @@ class PlaylistView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
heartBtn: PlaylistHeartButton(playlist: playlist),
|
heartBtn: PlaylistHeartButton(
|
||||||
|
playlist: playlist,
|
||||||
|
icon: ownPlaylist ? SpotubeIcons.trash : null,
|
||||||
|
onData: (data) {
|
||||||
|
GoRouter.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
onShuffledPlay: ([track]) {
|
onShuffledPlay: ([track]) {
|
||||||
final tracks = [...?tracksSnapshot.data]..shuffle();
|
final tracks = [...?tracksSnapshot.data]..shuffle();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
@ -11,7 +11,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:piped_client/piped_client.dart';
|
import 'package:piped_client/piped_client.dart';
|
||||||
import 'package:spotube/collections/env.dart';
|
import 'package:spotube/collections/env.dart';
|
||||||
import 'package:spotube/collections/language_codes.dart';
|
import 'package:spotube/collections/language_codes.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
||||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||||
@ -47,8 +46,8 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
final pickDownloadLocation = useCallback(() async {
|
final pickDownloadLocation = useCallback(() async {
|
||||||
final dirStr = await FilePicker.platform.getDirectoryPath(
|
final dirStr = await getDirectoryPath(
|
||||||
dialogTitle: context.l10n.download_location,
|
initialDirectory: preferences.downloadLocation,
|
||||||
);
|
);
|
||||||
if (dirStr == null) return;
|
if (dirStr == null) return;
|
||||||
preferences.setDownloadLocation(dirStr);
|
preferences.setDownloadLocation(dirStr);
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:fl_query_hooks/fl_query_hooks.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/hooks/use_spotify_mutation.dart';
|
import 'package:spotube/hooks/use_spotify_mutation.dart';
|
||||||
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
|
|
||||||
|
typedef PlaylistCRUDVariables = ({
|
||||||
|
String playlistName,
|
||||||
|
bool? public,
|
||||||
|
bool? collaborative,
|
||||||
|
String? description,
|
||||||
|
String? base64Image,
|
||||||
|
});
|
||||||
|
|
||||||
class PlaylistMutations {
|
class PlaylistMutations {
|
||||||
const PlaylistMutations();
|
const PlaylistMutations();
|
||||||
@ -11,8 +21,8 @@ class PlaylistMutations {
|
|||||||
String playlistId, {
|
String playlistId, {
|
||||||
List<String>? refreshQueries,
|
List<String>? refreshQueries,
|
||||||
List<String>? refreshInfiniteQueries,
|
List<String>? refreshInfiniteQueries,
|
||||||
|
ValueChanged<bool>? onData,
|
||||||
}) {
|
}) {
|
||||||
final queryClient = useQueryClient();
|
|
||||||
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
return useSpotifyMutation<bool, dynamic, bool, dynamic>(
|
||||||
"toggle-playlist-like/$playlistId",
|
"toggle-playlist-like/$playlistId",
|
||||||
(isLiked, spotify) async {
|
(isLiked, spotify) async {
|
||||||
@ -25,10 +35,12 @@ class PlaylistMutations {
|
|||||||
},
|
},
|
||||||
ref: ref,
|
ref: ref,
|
||||||
refreshQueries: refreshQueries,
|
refreshQueries: refreshQueries,
|
||||||
refreshInfiniteQueries: refreshInfiniteQueries,
|
refreshInfiniteQueries: [
|
||||||
onData: (data, recoveryData) async {
|
...?refreshInfiniteQueries,
|
||||||
await queryClient
|
"current-user-playlists",
|
||||||
.refreshInfiniteQueryAllPages("current-user-playlists");
|
],
|
||||||
|
onData: (data, recoveryData) {
|
||||||
|
onData?.call(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -47,4 +59,91 @@ class PlaylistMutations {
|
|||||||
refreshQueries: ["playlist-tracks/$playlistId"],
|
refreshQueries: ["playlist-tracks/$playlistId"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mutation<Playlist, dynamic, PlaylistCRUDVariables> create(
|
||||||
|
WidgetRef ref, {
|
||||||
|
List<String>? trackIds,
|
||||||
|
ValueChanged<dynamic>? onError,
|
||||||
|
ValueChanged<Playlist>? onData,
|
||||||
|
}) {
|
||||||
|
final me = useQueries.user.me(ref);
|
||||||
|
return useSpotifyMutation<Playlist, dynamic, PlaylistCRUDVariables, void>(
|
||||||
|
"create-playlist",
|
||||||
|
(variable, spotify) async {
|
||||||
|
final playlist = await spotify.playlists.createPlaylist(
|
||||||
|
me.data!.id!,
|
||||||
|
variable.playlistName,
|
||||||
|
collaborative: variable.collaborative,
|
||||||
|
description: variable.description,
|
||||||
|
public: variable.public,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (variable.base64Image != null) {
|
||||||
|
await spotify.playlists.updatePlaylistImage(
|
||||||
|
playlist.id!,
|
||||||
|
variable.base64Image!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackIds != null && trackIds.isNotEmpty) {
|
||||||
|
await spotify.playlists.addTracks(
|
||||||
|
trackIds.map((id) => "spotify:track:$id").toList(),
|
||||||
|
playlist.id!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
},
|
||||||
|
refreshInfiniteQueries: [
|
||||||
|
"current-user-playlists",
|
||||||
|
],
|
||||||
|
ref: ref,
|
||||||
|
onError: (error, recoveryData) {
|
||||||
|
onError?.call(error);
|
||||||
|
},
|
||||||
|
onData: (data, recoveryData) {
|
||||||
|
onData?.call(data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutation<void, dynamic, PlaylistCRUDVariables> update(
|
||||||
|
WidgetRef ref, {
|
||||||
|
String? playlistId,
|
||||||
|
ValueChanged<dynamic>? onError,
|
||||||
|
ValueChanged<void>? onData,
|
||||||
|
}) {
|
||||||
|
return useSpotifyMutation<void, dynamic, PlaylistCRUDVariables, void>(
|
||||||
|
"update-playlist/$playlistId",
|
||||||
|
(variable, spotify) async {
|
||||||
|
if (playlistId == null) return;
|
||||||
|
await spotify.playlists.updatePlaylist(
|
||||||
|
playlistId,
|
||||||
|
variable.playlistName,
|
||||||
|
collaborative: variable.collaborative,
|
||||||
|
description: variable.description,
|
||||||
|
public: variable.public,
|
||||||
|
);
|
||||||
|
if (variable.base64Image != null) {
|
||||||
|
await spotify.playlists.updatePlaylistImage(
|
||||||
|
playlistId,
|
||||||
|
variable.base64Image!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refreshQueries: [
|
||||||
|
"playlist/$playlistId",
|
||||||
|
],
|
||||||
|
refreshInfiniteQueries: [
|
||||||
|
"current-user-playlists",
|
||||||
|
],
|
||||||
|
ref: ref,
|
||||||
|
onError: (error, recoveryData) {
|
||||||
|
onError?.call(error);
|
||||||
|
},
|
||||||
|
onData: (data, recoveryData) {
|
||||||
|
onData?.call(data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +203,16 @@ class PlaylistQueries {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Query<Playlist, dynamic> byId(WidgetRef ref, String id) {
|
||||||
|
return useSpotifyQuery<Playlist, dynamic>(
|
||||||
|
"playlist/$id",
|
||||||
|
(spotify) async {
|
||||||
|
return await spotify.playlists.get(id);
|
||||||
|
},
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> featured(
|
InfiniteQuery<Page<PlaylistSimple>, dynamic, int> featured(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) {
|
) {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <catcher/catcher_plugin.h>
|
#include <catcher/catcher_plugin.h>
|
||||||
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <local_notifier/local_notifier_plugin.h>
|
#include <local_notifier/local_notifier_plugin.h>
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) catcher_registrar =
|
g_autoptr(FlPluginRegistrar) catcher_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "CatcherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "CatcherPlugin");
|
||||||
catcher_plugin_register_with_registrar(catcher_registrar);
|
catcher_plugin_register_with_registrar(catcher_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
catcher
|
catcher
|
||||||
|
file_selector_linux
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
local_notifier
|
local_notifier
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
|
@ -9,6 +9,7 @@ import audio_service
|
|||||||
import audio_session
|
import audio_session
|
||||||
import catcher
|
import catcher
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
|
import file_selector_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import local_notifier
|
import local_notifier
|
||||||
import media_kit_libs_macos_audio
|
import media_kit_libs_macos_audio
|
||||||
@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
||||||
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
||||||
|
@ -19,5 +19,8 @@
|
|||||||
<!-- Requires Certification -->
|
<!-- Requires Certification -->
|
||||||
<!-- <key>keychain-access-groups</key>
|
<!-- <key>keychain-access-groups</key>
|
||||||
<array /> -->
|
<array /> -->
|
||||||
|
<!-- FilePicker -->
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true />
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@ -31,9 +31,9 @@
|
|||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true />
|
||||||
<key>NSAllowsArbitraryLoadsForMedia</key>
|
<key>NSAllowsArbitraryLoadsForMedia</key>
|
||||||
<true/>
|
<true />
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
@ -17,5 +17,8 @@
|
|||||||
<!-- Requires Certification -->
|
<!-- Requires Certification -->
|
||||||
<!-- <key>keychain-access-groups</key>
|
<!-- <key>keychain-access-groups</key>
|
||||||
<array /> -->
|
<array /> -->
|
||||||
|
<!-- FilePicker -->
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true />
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
144
pubspec.lock
144
pubspec.lock
@ -338,6 +338,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3+5"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -498,14 +506,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
file_picker:
|
file_selector:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_selector
|
||||||
sha256: c7a8e25ca60e7f331b153b0cb3d405828f18d3e72a6fa1d9440c86556fffc877
|
sha256: "84eaf3e034d647859167d1f01cfe7b6352488f34c1b4932635012b202014c25b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.3.0"
|
version: "1.0.1"
|
||||||
|
file_selector_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_android
|
||||||
|
sha256: d41e165d6f798ca941d536e5dc93494d50e78c571c28ad60cfe0b0fefeb9f1e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0+3"
|
||||||
|
file_selector_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_ios
|
||||||
|
sha256: b3fbdda64aa2e335df6e111f6b0f1bb968402ed81d2dd1fa4274267999aa32c2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.1+6"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.2+1"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "182c3f8350cee659f7b115e956047ee3dc672a96665883a545e81581b9a82c72"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+2"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: "0aa47a725c346825a2bd396343ce63ac00bda6eff2fbc43eabe99737dede8262"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.1"
|
||||||
|
file_selector_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_web
|
||||||
|
sha256: dc6622c4d66cb1bee623ddcc029036603c6cc45c85e4a775bb06008d61c809c1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.2+1"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+1"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -788,6 +852,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.3"
|
version: "8.1.3"
|
||||||
|
form_validator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: form_validator
|
||||||
|
sha256: "8cbe91b7d5260870d6fb9e23acd55d5d1d1fdf2397f0279a4931ac3c0c7bf8fb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -945,6 +1017,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.17"
|
version: "4.0.17"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: d32a997bcc4ee135aebca8e272b7c517927aa65a74b9c60a81a2764ef1a0462d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.7+5"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "50bc9ae6a77eea3a8b11af5eb6c661eeb858fdd2f734c2a4fd17086922347ef7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.8+2"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.9.1"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
integration_test:
|
integration_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -33,7 +33,6 @@ dependencies:
|
|||||||
disable_battery_optimization: ^1.1.0+1
|
disable_battery_optimization: ^1.1.0+1
|
||||||
duration: ^3.0.12
|
duration: ^3.0.12
|
||||||
envied: ^0.3.0
|
envied: ^0.3.0
|
||||||
file_picker: ^5.2.2
|
|
||||||
fl_query: ^1.0.0-alpha.4
|
fl_query: ^1.0.0-alpha.4
|
||||||
fl_query_hooks: ^1.0.0-alpha.4+1
|
fl_query_hooks: ^1.0.0-alpha.4+1
|
||||||
fl_query_devtools: ^0.1.0-alpha.2
|
fl_query_devtools: ^0.1.0-alpha.2
|
||||||
@ -55,6 +54,7 @@ dependencies:
|
|||||||
flutter_riverpod: ^2.1.1
|
flutter_riverpod: ^2.1.1
|
||||||
flutter_secure_storage: ^8.0.0
|
flutter_secure_storage: ^8.0.0
|
||||||
flutter_svg: ^1.1.6
|
flutter_svg: ^1.1.6
|
||||||
|
form_validator: ^2.1.1
|
||||||
fuzzywuzzy: ^0.2.0
|
fuzzywuzzy: ^0.2.0
|
||||||
google_fonts: ^5.1.0
|
google_fonts: ^5.1.0
|
||||||
go_router: ^10.0.0
|
go_router: ^10.0.0
|
||||||
@ -100,6 +100,8 @@ dependencies:
|
|||||||
path: plugins/window_size
|
path: plugins/window_size
|
||||||
youtube_explode_dart: ^2.0.1
|
youtube_explode_dart: ^2.0.1
|
||||||
stroke_text: ^0.0.2
|
stroke_text: ^0.0.2
|
||||||
|
image_picker: ^1.0.4
|
||||||
|
file_selector: ^1.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.3.2
|
build_runner: ^2.3.2
|
||||||
|
@ -1 +1,56 @@
|
|||||||
{}
|
{
|
||||||
|
"bn": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ca": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"de": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"hi": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ja": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pl": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pt": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ru": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
|
||||||
|
"zh": [
|
||||||
|
"update_playlist",
|
||||||
|
"update"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <catcher/catcher_plugin.h>
|
#include <catcher/catcher_plugin.h>
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <local_notifier/local_notifier_plugin.h>
|
#include <local_notifier/local_notifier_plugin.h>
|
||||||
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
|
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
|
||||||
@ -21,6 +22,8 @@
|
|||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
CatcherPluginRegisterWithRegistrar(
|
CatcherPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("CatcherPlugin"));
|
registry->GetRegistrarForPlugin("CatcherPlugin"));
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
LocalNotifierPluginRegisterWithRegistrar(
|
LocalNotifierPluginRegisterWithRegistrar(
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
catcher
|
catcher
|
||||||
|
file_selector_windows
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
local_notifier
|
local_notifier
|
||||||
media_kit_libs_windows_audio
|
media_kit_libs_windows_audio
|
||||||
|
Loading…
Reference in New Issue
Block a user