✨ Better explore ever
This commit is contained in:
		@@ -15,6 +15,8 @@ PODS:
 | 
			
		||||
  - flutter_inappwebview_ios/Core (0.0.1):
 | 
			
		||||
    - Flutter
 | 
			
		||||
    - OrderedSet (~> 5.0)
 | 
			
		||||
  - flutter_native_splash (0.0.1):
 | 
			
		||||
    - Flutter
 | 
			
		||||
  - flutter_secure_storage (6.0.0):
 | 
			
		||||
    - Flutter
 | 
			
		||||
  - media_kit_libs_ios_audio (1.0.4):
 | 
			
		||||
@@ -61,6 +63,7 @@ DEPENDENCIES:
 | 
			
		||||
  - Flutter (from `Flutter`)
 | 
			
		||||
  - flutter_broadcasts (from `.symlinks/plugins/flutter_broadcasts/ios`)
 | 
			
		||||
  - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
 | 
			
		||||
  - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
 | 
			
		||||
  - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
 | 
			
		||||
  - media_kit_libs_ios_audio (from `.symlinks/plugins/media_kit_libs_ios_audio/ios`)
 | 
			
		||||
  - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
 | 
			
		||||
@@ -89,6 +92,8 @@ EXTERNAL SOURCES:
 | 
			
		||||
    :path: ".symlinks/plugins/flutter_broadcasts/ios"
 | 
			
		||||
  flutter_inappwebview_ios:
 | 
			
		||||
    :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
 | 
			
		||||
  flutter_native_splash:
 | 
			
		||||
    :path: ".symlinks/plugins/flutter_native_splash/ios"
 | 
			
		||||
  flutter_secure_storage:
 | 
			
		||||
    :path: ".symlinks/plugins/flutter_secure_storage/ios"
 | 
			
		||||
  media_kit_libs_ios_audio:
 | 
			
		||||
@@ -115,6 +120,7 @@ SPEC CHECKSUMS:
 | 
			
		||||
  Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
 | 
			
		||||
  flutter_broadcasts: 3ece15b27d8ccbe2132c3df303e7c3401feab882
 | 
			
		||||
  flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
 | 
			
		||||
  flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
 | 
			
		||||
  flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
 | 
			
		||||
  media_kit_libs_ios_audio: 8f39d96a9c630685dfb844c289bd1d114c486fb3
 | 
			
		||||
  media_kit_native_event_loop: 99111eded5acbdc9c2738021ea6550dd36ca8837
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,16 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:gap/gap.dart';
 | 
			
		||||
import 'package:get/get.dart';
 | 
			
		||||
import 'package:intl/intl.dart';
 | 
			
		||||
import 'package:rhythm_box/providers/auth.dart';
 | 
			
		||||
import 'package:rhythm_box/providers/recent_played.dart';
 | 
			
		||||
import 'package:rhythm_box/providers/spotify.dart';
 | 
			
		||||
import 'package:rhythm_box/providers/user_preferences.dart';
 | 
			
		||||
import 'package:rhythm_box/services/album.dart';
 | 
			
		||||
import 'package:rhythm_box/services/database/database.dart';
 | 
			
		||||
import 'package:rhythm_box/services/spotify/spotify_endpoints.dart';
 | 
			
		||||
import 'package:rhythm_box/widgets/playlist/playlist_section.dart';
 | 
			
		||||
import 'package:spotify/spotify.dart';
 | 
			
		||||
 | 
			
		||||
class ExploreScreen extends StatefulWidget {
 | 
			
		||||
  const ExploreScreen({super.key});
 | 
			
		||||
@@ -18,19 +22,23 @@ class ExploreScreen extends StatefulWidget {
 | 
			
		||||
class _ExploreScreenState extends State<ExploreScreen> {
 | 
			
		||||
  late final SpotifyProvider _spotify = Get.find();
 | 
			
		||||
  late final RecentlyPlayedProvider _history = Get.find();
 | 
			
		||||
  late final AuthenticationProvider _auth = Get.find();
 | 
			
		||||
 | 
			
		||||
  final Map<String, bool> _isLoading = {
 | 
			
		||||
    'featured': true,
 | 
			
		||||
    'recently': true,
 | 
			
		||||
    'newReleases': true,
 | 
			
		||||
    'forYou': true,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  List<Object>? _featuredPlaylist;
 | 
			
		||||
  List<Object>? _recentlyPlaylist;
 | 
			
		||||
  List<Object>? _newReleasesPlaylist;
 | 
			
		||||
  List<dynamic>? _forYouView;
 | 
			
		||||
 | 
			
		||||
  Future<void> _pullPlaylist() async {
 | 
			
		||||
    final market = Get.find<UserPreferencesProvider>().state.value.market;
 | 
			
		||||
    final locale = Get.find<UserPreferencesProvider>().state.value.locale;
 | 
			
		||||
 | 
			
		||||
    _featuredPlaylist =
 | 
			
		||||
        (await _spotify.api.playlists.featured.getPage(20)).items!.toList();
 | 
			
		||||
@@ -48,6 +56,16 @@ class _ExploreScreenState extends State<ExploreScreen> {
 | 
			
		||||
            ?.map((album) => album.toAlbum())
 | 
			
		||||
            .toList();
 | 
			
		||||
    setState(() => _isLoading['newReleases'] = false);
 | 
			
		||||
 | 
			
		||||
    final customEndpoint =
 | 
			
		||||
        CustomSpotifyEndpoints(_auth.auth.value?.accessToken.value ?? '');
 | 
			
		||||
    final forYouView = await customEndpoint.getView(
 | 
			
		||||
      'made-for-x-hub',
 | 
			
		||||
      market: market,
 | 
			
		||||
      locale: Intl.canonicalizedLocale(locale.toString()),
 | 
			
		||||
    );
 | 
			
		||||
    _forYouView = forYouView['content']?['items'];
 | 
			
		||||
    setState(() => _isLoading['forYou'] = false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -65,28 +83,52 @@ class _ExploreScreenState extends State<ExploreScreen> {
 | 
			
		||||
          title: Text('explore'.tr),
 | 
			
		||||
          centerTitle: MediaQuery.of(context).size.width >= 720,
 | 
			
		||||
        ),
 | 
			
		||||
        body: ListView(
 | 
			
		||||
          children: [
 | 
			
		||||
        body: CustomScrollView(
 | 
			
		||||
          slivers: [
 | 
			
		||||
            if (_newReleasesPlaylist?.isNotEmpty ?? false)
 | 
			
		||||
              PlaylistSection(
 | 
			
		||||
              SliverToBoxAdapter(
 | 
			
		||||
                child: PlaylistSection(
 | 
			
		||||
                  isLoading: _isLoading['newReleases']!,
 | 
			
		||||
                  title: 'New Releases',
 | 
			
		||||
                  list: _newReleasesPlaylist,
 | 
			
		||||
                ),
 | 
			
		||||
            if (_newReleasesPlaylist?.isNotEmpty ?? false) const Gap(16),
 | 
			
		||||
              ),
 | 
			
		||||
            if (_newReleasesPlaylist?.isNotEmpty ?? false) const SliverGap(16),
 | 
			
		||||
            if (_recentlyPlaylist?.isNotEmpty ?? false)
 | 
			
		||||
              PlaylistSection(
 | 
			
		||||
              SliverToBoxAdapter(
 | 
			
		||||
                child: PlaylistSection(
 | 
			
		||||
                  isLoading: _isLoading['recently']!,
 | 
			
		||||
                  title: 'Recent Played',
 | 
			
		||||
                  list: _recentlyPlaylist,
 | 
			
		||||
                ),
 | 
			
		||||
            if (_recentlyPlaylist?.isNotEmpty ?? false) const Gap(16),
 | 
			
		||||
            PlaylistSection(
 | 
			
		||||
              ),
 | 
			
		||||
            if (_recentlyPlaylist?.isNotEmpty ?? false) const SliverGap(16),
 | 
			
		||||
            SliverList.builder(
 | 
			
		||||
              itemCount: _forYouView?.length ?? 0,
 | 
			
		||||
              itemBuilder: (context, idx) {
 | 
			
		||||
                final item = _forYouView![idx];
 | 
			
		||||
                final playlists = item['content']?['items']
 | 
			
		||||
                        ?.where((itemL2) => itemL2['type'] == 'playlist')
 | 
			
		||||
                        .map((itemL2) => PlaylistSimple.fromJson(itemL2))
 | 
			
		||||
                        .toList()
 | 
			
		||||
                        .cast<PlaylistSimple>() ??
 | 
			
		||||
                    <PlaylistSimple>[];
 | 
			
		||||
                if (playlists.isEmpty) return const SizedBox.shrink();
 | 
			
		||||
                return PlaylistSection(
 | 
			
		||||
                  isLoading: false,
 | 
			
		||||
                  title: item['name'] ?? '',
 | 
			
		||||
                  list: playlists,
 | 
			
		||||
                ).paddingOnly(bottom: 16);
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
            SliverToBoxAdapter(
 | 
			
		||||
              child: PlaylistSection(
 | 
			
		||||
                isLoading: _isLoading['featured']!,
 | 
			
		||||
                title: 'Featured',
 | 
			
		||||
                list: _featuredPlaylist,
 | 
			
		||||
              ),
 | 
			
		||||
            const Gap(16),
 | 
			
		||||
            ),
 | 
			
		||||
            const SliverGap(16),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								lib/services/song_link/song_link.freezed.dart
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/services/song_link/song_link.freezed.dart
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -30,8 +30,12 @@ mixin _$SongLink {
 | 
			
		||||
  String? get nativeAppUriMobile => throw _privateConstructorUsedError;
 | 
			
		||||
  String? get nativeAppUriDesktop => throw _privateConstructorUsedError;
 | 
			
		||||
 | 
			
		||||
  /// Serializes this SongLink to a JSON map.
 | 
			
		||||
  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
 | 
			
		||||
  @JsonKey(ignore: true)
 | 
			
		||||
 | 
			
		||||
  /// Create a copy of SongLink
 | 
			
		||||
  /// with the given fields replaced by the non-null parameter values.
 | 
			
		||||
  @JsonKey(includeFromJson: false, includeToJson: false)
 | 
			
		||||
  $SongLinkCopyWith<SongLink> get copyWith =>
 | 
			
		||||
      throw _privateConstructorUsedError;
 | 
			
		||||
}
 | 
			
		||||
@@ -63,6 +67,8 @@ class _$SongLinkCopyWithImpl<$Res, $Val extends SongLink>
 | 
			
		||||
  // ignore: unused_field
 | 
			
		||||
  final $Res Function($Val) _then;
 | 
			
		||||
 | 
			
		||||
  /// Create a copy of SongLink
 | 
			
		||||
  /// with the given fields replaced by the non-null parameter values.
 | 
			
		||||
  @pragma('vm:prefer-inline')
 | 
			
		||||
  @override
 | 
			
		||||
  $Res call({
 | 
			
		||||
@@ -145,6 +151,8 @@ class __$$SongLinkImplCopyWithImpl<$Res>
 | 
			
		||||
      _$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then)
 | 
			
		||||
      : super(_value, _then);
 | 
			
		||||
 | 
			
		||||
  /// Create a copy of SongLink
 | 
			
		||||
  /// with the given fields replaced by the non-null parameter values.
 | 
			
		||||
  @pragma('vm:prefer-inline')
 | 
			
		||||
  @override
 | 
			
		||||
  $Res call({
 | 
			
		||||
@@ -261,12 +269,14 @@ class _$SongLinkImpl implements _SongLink {
 | 
			
		||||
                other.nativeAppUriDesktop == nativeAppUriDesktop));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JsonKey(ignore: true)
 | 
			
		||||
  @JsonKey(includeFromJson: false, includeToJson: false)
 | 
			
		||||
  @override
 | 
			
		||||
  int get hashCode => Object.hash(runtimeType, displayName, linkId, platform,
 | 
			
		||||
      show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop);
 | 
			
		||||
 | 
			
		||||
  @JsonKey(ignore: true)
 | 
			
		||||
  /// Create a copy of SongLink
 | 
			
		||||
  /// with the given fields replaced by the non-null parameter values.
 | 
			
		||||
  @JsonKey(includeFromJson: false, includeToJson: false)
 | 
			
		||||
  @override
 | 
			
		||||
  @pragma('vm:prefer-inline')
 | 
			
		||||
  _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
 | 
			
		||||
@@ -313,8 +323,11 @@ abstract class _SongLink implements SongLink {
 | 
			
		||||
  String? get nativeAppUriMobile;
 | 
			
		||||
  @override
 | 
			
		||||
  String? get nativeAppUriDesktop;
 | 
			
		||||
 | 
			
		||||
  /// Create a copy of SongLink
 | 
			
		||||
  /// with the given fields replaced by the non-null parameter values.
 | 
			
		||||
  @override
 | 
			
		||||
  @JsonKey(ignore: true)
 | 
			
		||||
  @JsonKey(includeFromJson: false, includeToJson: false)
 | 
			
		||||
  _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
 | 
			
		||||
      throw _privateConstructorUsedError;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										214
									
								
								lib/services/spotify/spotify_endpoints.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								lib/services/spotify/spotify_endpoints.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:dio/dio.dart';
 | 
			
		||||
import 'package:rhythm_box/services/spotify/spotify_feed.dart';
 | 
			
		||||
import 'package:spotify/spotify.dart';
 | 
			
		||||
import 'package:timezone/timezone.dart' as tz;
 | 
			
		||||
 | 
			
		||||
class CustomSpotifyEndpoints {
 | 
			
		||||
  static const _baseUrl = 'https://api.spotify.com/v1';
 | 
			
		||||
  final String accessToken;
 | 
			
		||||
  final Dio _client;
 | 
			
		||||
 | 
			
		||||
  CustomSpotifyEndpoints(this.accessToken)
 | 
			
		||||
      : _client = Dio(
 | 
			
		||||
          BaseOptions(
 | 
			
		||||
            baseUrl: _baseUrl,
 | 
			
		||||
            responseType: ResponseType.json,
 | 
			
		||||
            headers: {
 | 
			
		||||
              'content-type': 'application/json',
 | 
			
		||||
              if (accessToken.isNotEmpty)
 | 
			
		||||
                'authorization': 'Bearer $accessToken',
 | 
			
		||||
              'accept': 'application/json',
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
  // views API
 | 
			
		||||
 | 
			
		||||
  /// Get a single view of given genre
 | 
			
		||||
  ///
 | 
			
		||||
  /// Currently known genres are:
 | 
			
		||||
  /// - new-releases-page
 | 
			
		||||
  /// - made-for-x-hub (it requires authentication)
 | 
			
		||||
  /// - my-mix-genres (it requires authentication)
 | 
			
		||||
  /// - artist-seed-mixes (it requires authentication)
 | 
			
		||||
  /// - my-mix-decades (it requires authentication)
 | 
			
		||||
  /// - my-mix-moods (it requires authentication)
 | 
			
		||||
  /// - podcasts-and-more (it requires authentication)
 | 
			
		||||
  /// - uniquely-yours-in-hub (it requires authentication)
 | 
			
		||||
  /// - made-for-x-dailymix (it requires authentication)
 | 
			
		||||
  /// - made-for-x-discovery (it requires authentication)
 | 
			
		||||
  Future<Map<String, dynamic>> getView(
 | 
			
		||||
    String view, {
 | 
			
		||||
    int limit = 20,
 | 
			
		||||
    int contentLimit = 10,
 | 
			
		||||
    List<String> types = const [
 | 
			
		||||
      'album',
 | 
			
		||||
      'playlist',
 | 
			
		||||
      'artist',
 | 
			
		||||
      'show',
 | 
			
		||||
      'station',
 | 
			
		||||
      'episode',
 | 
			
		||||
      'merch',
 | 
			
		||||
      'artist_concerts',
 | 
			
		||||
      'uri_link'
 | 
			
		||||
    ],
 | 
			
		||||
    String imageStyle = 'gradient_overlay',
 | 
			
		||||
    String includeExternal = 'audio',
 | 
			
		||||
    String? locale,
 | 
			
		||||
    Market? market,
 | 
			
		||||
    Market? country,
 | 
			
		||||
  }) async {
 | 
			
		||||
    if (accessToken.isEmpty) {
 | 
			
		||||
      throw Exception('[CustomSpotifyEndpoints.getView]: accessToken is empty');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final queryParams = {
 | 
			
		||||
      'limit': limit.toString(),
 | 
			
		||||
      'content_limit': contentLimit.toString(),
 | 
			
		||||
      'types': types.join(','),
 | 
			
		||||
      'image_style': imageStyle,
 | 
			
		||||
      'include_external': includeExternal,
 | 
			
		||||
      'timestamp': DateTime.now().toUtc().toIso8601String(),
 | 
			
		||||
      if (locale != null) 'locale': locale,
 | 
			
		||||
      if (market != null) 'market': market.name,
 | 
			
		||||
      if (country != null) 'country': country.name,
 | 
			
		||||
    }.entries.map((e) => '${e.key}=${e.value}').join('&');
 | 
			
		||||
 | 
			
		||||
    final res = await _client.getUri(
 | 
			
		||||
      Uri.parse('$_baseUrl/views/$view?$queryParams'),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      return res.data;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw Exception(
 | 
			
		||||
        '[CustomSpotifyEndpoints.getView]: Failed to get view'
 | 
			
		||||
        '\nStatus code: ${res.statusCode}'
 | 
			
		||||
        '\nBody: ${res.data}',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<List<String>> listGenreSeeds() async {
 | 
			
		||||
    final res = await _client.getUri(
 | 
			
		||||
      Uri.parse('$_baseUrl/recommendations/available-genre-seeds'),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      final body = res.data;
 | 
			
		||||
      return List<String>.from(body['genres'] ?? []);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw Exception(
 | 
			
		||||
        '[CustomSpotifyEndpoints.listGenreSeeds]: Failed to get genre seeds'
 | 
			
		||||
        '\nStatus code: ${res.statusCode}'
 | 
			
		||||
        '\nBody: ${res.data}',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<SpotifyHomeFeed> getHomeFeed({
 | 
			
		||||
    required String spTCookie,
 | 
			
		||||
    required Market country,
 | 
			
		||||
  }) async {
 | 
			
		||||
    final headers = {
 | 
			
		||||
      'app-platform': 'WebPlayer',
 | 
			
		||||
      'authorization': 'Bearer $accessToken',
 | 
			
		||||
      'content-type': 'application/json;charset=UTF-8',
 | 
			
		||||
      'dnt': '1',
 | 
			
		||||
      'origin': 'https://open.spotify.com',
 | 
			
		||||
      'referer': 'https://open.spotify.com/'
 | 
			
		||||
    };
 | 
			
		||||
    final response = await _client.getUri(
 | 
			
		||||
      Uri(
 | 
			
		||||
        scheme: 'https',
 | 
			
		||||
        host: 'api-partner.spotify.com',
 | 
			
		||||
        path: '/pathfinder/v1/query',
 | 
			
		||||
        queryParameters: {
 | 
			
		||||
          'operationName': 'home',
 | 
			
		||||
          'variables': jsonEncode({
 | 
			
		||||
            'timeZone': tz.local.name,
 | 
			
		||||
            'sp_t': spTCookie,
 | 
			
		||||
            'country': country.name,
 | 
			
		||||
            'facet': null,
 | 
			
		||||
            'sectionItemsLimit': 10
 | 
			
		||||
          }),
 | 
			
		||||
          'extensions': jsonEncode(
 | 
			
		||||
            {
 | 
			
		||||
              'persistedQuery': {
 | 
			
		||||
                'version': 1,
 | 
			
		||||
 | 
			
		||||
                /// GraphQL persisted Query hash
 | 
			
		||||
                /// This can change overtime. We've to lookout for it
 | 
			
		||||
                /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/
 | 
			
		||||
                'sha256Hash':
 | 
			
		||||
                    'eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be',
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
      options: Options(headers: headers),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    final data = SpotifyHomeFeed.fromJson(
 | 
			
		||||
      transformHomeFeedJsonMap(response.data),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<SpotifyHomeFeedSection> getHomeFeedSection(
 | 
			
		||||
    String sectionUri, {
 | 
			
		||||
    required String spTCookie,
 | 
			
		||||
    required Market country,
 | 
			
		||||
  }) async {
 | 
			
		||||
    final headers = {
 | 
			
		||||
      'app-platform': 'WebPlayer',
 | 
			
		||||
      'authorization': 'Bearer $accessToken',
 | 
			
		||||
      'content-type': 'application/json;charset=UTF-8',
 | 
			
		||||
      'dnt': '1',
 | 
			
		||||
      'origin': 'https://open.spotify.com',
 | 
			
		||||
      'referer': 'https://open.spotify.com/'
 | 
			
		||||
    };
 | 
			
		||||
    final response = await _client.getUri(
 | 
			
		||||
      Uri(
 | 
			
		||||
        scheme: 'https',
 | 
			
		||||
        host: 'api-partner.spotify.com',
 | 
			
		||||
        path: '/pathfinder/v1/query',
 | 
			
		||||
        queryParameters: {
 | 
			
		||||
          'operationName': 'homeSection',
 | 
			
		||||
          'variables': jsonEncode({
 | 
			
		||||
            'timeZone': tz.local.name,
 | 
			
		||||
            'sp_t': spTCookie,
 | 
			
		||||
            'country': country.name,
 | 
			
		||||
            'uri': sectionUri
 | 
			
		||||
          }),
 | 
			
		||||
          'extensions': jsonEncode(
 | 
			
		||||
            {
 | 
			
		||||
              'persistedQuery': {
 | 
			
		||||
                'version': 1,
 | 
			
		||||
 | 
			
		||||
                /// GraphQL persisted Query hash
 | 
			
		||||
                /// This can change overtime. We've to lookout for it
 | 
			
		||||
                /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/
 | 
			
		||||
                'sha256Hash':
 | 
			
		||||
                    'eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be',
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
      options: Options(headers: headers),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    final data = SpotifyHomeFeedSection.fromJson(
 | 
			
		||||
      transformSectionItemJsonMap(
 | 
			
		||||
        response.data['data']['homeSections']['sections'][0],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										247
									
								
								lib/services/spotify/spotify_feed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								lib/services/spotify/spotify_feed.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
import 'package:freezed_annotation/freezed_annotation.dart';
 | 
			
		||||
import 'package:spotify/spotify.dart';
 | 
			
		||||
 | 
			
		||||
part 'spotify_feed.freezed.dart';
 | 
			
		||||
part 'spotify_feed.g.dart';
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifySectionPlaylist with _$SpotifySectionPlaylist {
 | 
			
		||||
  const SpotifySectionPlaylist._();
 | 
			
		||||
 | 
			
		||||
  const factory SpotifySectionPlaylist({
 | 
			
		||||
    required String description,
 | 
			
		||||
    required String format,
 | 
			
		||||
    required List<SpotifySectionItemImage> images,
 | 
			
		||||
    required String name,
 | 
			
		||||
    required String owner,
 | 
			
		||||
    required String uri,
 | 
			
		||||
  }) = _SpotifySectionPlaylist;
 | 
			
		||||
 | 
			
		||||
  factory SpotifySectionPlaylist.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifySectionPlaylistFromJson(json);
 | 
			
		||||
 | 
			
		||||
  String get id => uri.split(':').last;
 | 
			
		||||
 | 
			
		||||
  Playlist get asPlaylist {
 | 
			
		||||
    return Playlist()
 | 
			
		||||
      ..id = id
 | 
			
		||||
      ..name = name
 | 
			
		||||
      ..description = description
 | 
			
		||||
      ..collaborative = false
 | 
			
		||||
      ..images = images.map((e) => e.asImage).toList()
 | 
			
		||||
      ..owner = (User()..displayName = 'Spotify')
 | 
			
		||||
      ..uri = uri
 | 
			
		||||
      ..type = 'playlist';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifySectionArtist with _$SpotifySectionArtist {
 | 
			
		||||
  const SpotifySectionArtist._();
 | 
			
		||||
 | 
			
		||||
  const factory SpotifySectionArtist({
 | 
			
		||||
    required String name,
 | 
			
		||||
    required String uri,
 | 
			
		||||
    required List<SpotifySectionItemImage> images,
 | 
			
		||||
  }) = _SpotifySectionArtist;
 | 
			
		||||
 | 
			
		||||
  factory SpotifySectionArtist.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifySectionArtistFromJson(json);
 | 
			
		||||
 | 
			
		||||
  String get id => uri.split(':').last;
 | 
			
		||||
 | 
			
		||||
  Artist get asArtist {
 | 
			
		||||
    return Artist()
 | 
			
		||||
      ..id = id
 | 
			
		||||
      ..name = name
 | 
			
		||||
      ..images = images.map((e) => e.asImage).toList()
 | 
			
		||||
      ..type = 'artist'
 | 
			
		||||
      ..uri = uri;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifySectionAlbum with _$SpotifySectionAlbum {
 | 
			
		||||
  const SpotifySectionAlbum._();
 | 
			
		||||
 | 
			
		||||
  const factory SpotifySectionAlbum({
 | 
			
		||||
    required List<SpotifySectionAlbumArtist> artists,
 | 
			
		||||
    required List<SpotifySectionItemImage> images,
 | 
			
		||||
    required String name,
 | 
			
		||||
    required String uri,
 | 
			
		||||
  }) = _SpotifySectionAlbum;
 | 
			
		||||
 | 
			
		||||
  factory SpotifySectionAlbum.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifySectionAlbumFromJson(json);
 | 
			
		||||
 | 
			
		||||
  String get id => uri.split(':').last;
 | 
			
		||||
 | 
			
		||||
  Album get asAlbum {
 | 
			
		||||
    return Album()
 | 
			
		||||
      ..id = id
 | 
			
		||||
      ..name = name
 | 
			
		||||
      ..artists = artists.map((a) => a.asArtist).toList()
 | 
			
		||||
      ..albumType = AlbumType.album
 | 
			
		||||
      ..images = images.map((e) => e.asImage).toList()
 | 
			
		||||
      ..uri = uri;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifySectionAlbumArtist with _$SpotifySectionAlbumArtist {
 | 
			
		||||
  const SpotifySectionAlbumArtist._();
 | 
			
		||||
 | 
			
		||||
  const factory SpotifySectionAlbumArtist({
 | 
			
		||||
    required String name,
 | 
			
		||||
    required String uri,
 | 
			
		||||
  }) = _SpotifySectionAlbumArtist;
 | 
			
		||||
 | 
			
		||||
  factory SpotifySectionAlbumArtist.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifySectionAlbumArtistFromJson(json);
 | 
			
		||||
 | 
			
		||||
  String get id => uri.split(':').last;
 | 
			
		||||
 | 
			
		||||
  Artist get asArtist {
 | 
			
		||||
    return Artist()
 | 
			
		||||
      ..id = id
 | 
			
		||||
      ..name = name
 | 
			
		||||
      ..type = 'artist'
 | 
			
		||||
      ..uri = uri;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifySectionItemImage with _$SpotifySectionItemImage {
 | 
			
		||||
  const SpotifySectionItemImage._();
 | 
			
		||||
 | 
			
		||||
  const factory SpotifySectionItemImage({
 | 
			
		||||
    required num? height,
 | 
			
		||||
    required String url,
 | 
			
		||||
    required num? width,
 | 
			
		||||
  }) = _SpotifySectionItemImage;
 | 
			
		||||
 | 
			
		||||
  factory SpotifySectionItemImage.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifySectionItemImageFromJson(json);
 | 
			
		||||
 | 
			
		||||
  Image get asImage {
 | 
			
		||||
    return Image()
 | 
			
		||||
      ..height = height?.toInt()
 | 
			
		||||
      ..width = width?.toInt()
 | 
			
		||||
      ..url = url;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifyHomeFeedSectionItem with _$SpotifyHomeFeedSectionItem {
 | 
			
		||||
  factory SpotifyHomeFeedSectionItem({
 | 
			
		||||
    required String typename,
 | 
			
		||||
    SpotifySectionPlaylist? playlist,
 | 
			
		||||
    SpotifySectionArtist? artist,
 | 
			
		||||
    SpotifySectionAlbum? album,
 | 
			
		||||
  }) = _SpotifyHomeFeedSectionItem;
 | 
			
		||||
 | 
			
		||||
  factory SpotifyHomeFeedSectionItem.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifyHomeFeedSectionItemFromJson(json);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifyHomeFeedSection with _$SpotifyHomeFeedSection {
 | 
			
		||||
  factory SpotifyHomeFeedSection({
 | 
			
		||||
    required String typename,
 | 
			
		||||
    String? title,
 | 
			
		||||
    required String uri,
 | 
			
		||||
    required List<SpotifyHomeFeedSectionItem> items,
 | 
			
		||||
  }) = _SpotifyHomeFeedSection;
 | 
			
		||||
 | 
			
		||||
  factory SpotifyHomeFeedSection.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifyHomeFeedSectionFromJson(json);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@freezed
 | 
			
		||||
class SpotifyHomeFeed with _$SpotifyHomeFeed {
 | 
			
		||||
  factory SpotifyHomeFeed({
 | 
			
		||||
    required String greeting,
 | 
			
		||||
    required List<SpotifyHomeFeedSection> sections,
 | 
			
		||||
  }) = _SpotifyHomeFeed;
 | 
			
		||||
 | 
			
		||||
  factory SpotifyHomeFeed.fromJson(Map<String, dynamic> json) =>
 | 
			
		||||
      _$SpotifyHomeFeedFromJson(json);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> transformSectionItemTypeJsonMap(
 | 
			
		||||
    Map<String, dynamic> json) {
 | 
			
		||||
  final data = json['content']['data'];
 | 
			
		||||
  final objType = json['content']['data']['__typename'];
 | 
			
		||||
  return {
 | 
			
		||||
    'typename': json['content']['__typename'],
 | 
			
		||||
    if (objType == 'Playlist')
 | 
			
		||||
      'playlist': {
 | 
			
		||||
        'name': data['name'],
 | 
			
		||||
        'description': data['description'],
 | 
			
		||||
        'format': data['format'],
 | 
			
		||||
        'images': (data['images']['items'] as List)
 | 
			
		||||
            .expand((j) => j['sources'] as dynamic)
 | 
			
		||||
            .toList()
 | 
			
		||||
            .cast<Map<String, dynamic>>(),
 | 
			
		||||
        'owner': data['ownerV2']['data']['name'],
 | 
			
		||||
        'uri': data['uri']
 | 
			
		||||
      },
 | 
			
		||||
    if (objType == 'Artist')
 | 
			
		||||
      'artist': {
 | 
			
		||||
        'name': data['profile']['name'],
 | 
			
		||||
        'uri': data['uri'],
 | 
			
		||||
        'images': data['visuals']['avatarImage']['sources'],
 | 
			
		||||
      },
 | 
			
		||||
    if (objType == 'Album')
 | 
			
		||||
      'album': {
 | 
			
		||||
        'name': data['name'],
 | 
			
		||||
        'uri': data['uri'],
 | 
			
		||||
        'images': data['coverArt']['sources'],
 | 
			
		||||
        'artists': data['artists']['items']
 | 
			
		||||
            .map(
 | 
			
		||||
              (artist) => {
 | 
			
		||||
                'name': artist['profile']['name'],
 | 
			
		||||
                'uri': artist['uri'],
 | 
			
		||||
              },
 | 
			
		||||
            )
 | 
			
		||||
            .toList()
 | 
			
		||||
      },
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> transformSectionItemJsonMap(Map<String, dynamic> json) {
 | 
			
		||||
  return {
 | 
			
		||||
    'typename': json['data']['__typename'],
 | 
			
		||||
    'title': json['data']?['title']?['text'],
 | 
			
		||||
    'uri': json['uri'],
 | 
			
		||||
    'items': (json['sectionItems']['items'] as List)
 | 
			
		||||
        .map(
 | 
			
		||||
          (data) =>
 | 
			
		||||
              transformSectionItemTypeJsonMap(data as Map<String, dynamic>)
 | 
			
		||||
                  as dynamic,
 | 
			
		||||
        )
 | 
			
		||||
        .where(
 | 
			
		||||
          (w) =>
 | 
			
		||||
              w['playlist'] != null ||
 | 
			
		||||
              w['artist'] != null ||
 | 
			
		||||
              w['album'] != null,
 | 
			
		||||
        )
 | 
			
		||||
        .toList()
 | 
			
		||||
        .cast<Map<String, dynamic>>()
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> transformHomeFeedJsonMap(Map<String, dynamic> json) {
 | 
			
		||||
  return {
 | 
			
		||||
    'greeting': json['data']['home']['greeting']['text'],
 | 
			
		||||
    'sections':
 | 
			
		||||
        (json['data']['home']['sectionContainer']['sections']['items'] as List)
 | 
			
		||||
            .map(
 | 
			
		||||
              (item) =>
 | 
			
		||||
                  transformSectionItemJsonMap(item as Map<String, dynamic>)
 | 
			
		||||
                      as dynamic,
 | 
			
		||||
            )
 | 
			
		||||
            .toList()
 | 
			
		||||
            .cast<Map<String, dynamic>>()
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1776
									
								
								lib/services/spotify/spotify_feed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1776
									
								
								lib/services/spotify/spotify_feed.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										169
									
								
								lib/services/spotify/spotify_feed.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								lib/services/spotify/spotify_feed.g.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,169 @@
 | 
			
		||||
// GENERATED CODE - DO NOT MODIFY BY HAND
 | 
			
		||||
 | 
			
		||||
part of 'spotify_feed.dart';
 | 
			
		||||
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
// JsonSerializableGenerator
 | 
			
		||||
// **************************************************************************
 | 
			
		||||
 | 
			
		||||
_$SpotifySectionPlaylistImpl _$$SpotifySectionPlaylistImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifySectionPlaylistImpl(
 | 
			
		||||
      description: json['description'] as String,
 | 
			
		||||
      format: json['format'] as String,
 | 
			
		||||
      images: (json['images'] as List<dynamic>)
 | 
			
		||||
          .map((e) =>
 | 
			
		||||
              SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
      name: json['name'] as String,
 | 
			
		||||
      owner: json['owner'] as String,
 | 
			
		||||
      uri: json['uri'] as String,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifySectionPlaylistImplToJson(
 | 
			
		||||
        _$SpotifySectionPlaylistImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'description': instance.description,
 | 
			
		||||
      'format': instance.format,
 | 
			
		||||
      'images': instance.images,
 | 
			
		||||
      'name': instance.name,
 | 
			
		||||
      'owner': instance.owner,
 | 
			
		||||
      'uri': instance.uri,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifySectionArtistImpl _$$SpotifySectionArtistImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifySectionArtistImpl(
 | 
			
		||||
      name: json['name'] as String,
 | 
			
		||||
      uri: json['uri'] as String,
 | 
			
		||||
      images: (json['images'] as List<dynamic>)
 | 
			
		||||
          .map((e) =>
 | 
			
		||||
              SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifySectionArtistImplToJson(
 | 
			
		||||
        _$SpotifySectionArtistImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'name': instance.name,
 | 
			
		||||
      'uri': instance.uri,
 | 
			
		||||
      'images': instance.images,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifySectionAlbumImpl _$$SpotifySectionAlbumImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifySectionAlbumImpl(
 | 
			
		||||
      artists: (json['artists'] as List<dynamic>)
 | 
			
		||||
          .map((e) =>
 | 
			
		||||
              SpotifySectionAlbumArtist.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
      images: (json['images'] as List<dynamic>)
 | 
			
		||||
          .map((e) =>
 | 
			
		||||
              SpotifySectionItemImage.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
      name: json['name'] as String,
 | 
			
		||||
      uri: json['uri'] as String,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifySectionAlbumImplToJson(
 | 
			
		||||
        _$SpotifySectionAlbumImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'artists': instance.artists,
 | 
			
		||||
      'images': instance.images,
 | 
			
		||||
      'name': instance.name,
 | 
			
		||||
      'uri': instance.uri,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifySectionAlbumArtistImpl _$$SpotifySectionAlbumArtistImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifySectionAlbumArtistImpl(
 | 
			
		||||
      name: json['name'] as String,
 | 
			
		||||
      uri: json['uri'] as String,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifySectionAlbumArtistImplToJson(
 | 
			
		||||
        _$SpotifySectionAlbumArtistImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'name': instance.name,
 | 
			
		||||
      'uri': instance.uri,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifySectionItemImageImpl _$$SpotifySectionItemImageImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifySectionItemImageImpl(
 | 
			
		||||
      height: json['height'] as num?,
 | 
			
		||||
      url: json['url'] as String,
 | 
			
		||||
      width: json['width'] as num?,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifySectionItemImageImplToJson(
 | 
			
		||||
        _$SpotifySectionItemImageImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'height': instance.height,
 | 
			
		||||
      'url': instance.url,
 | 
			
		||||
      'width': instance.width,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifyHomeFeedSectionItemImpl _$$SpotifyHomeFeedSectionItemImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifyHomeFeedSectionItemImpl(
 | 
			
		||||
      typename: json['typename'] as String,
 | 
			
		||||
      playlist: json['playlist'] == null
 | 
			
		||||
          ? null
 | 
			
		||||
          : SpotifySectionPlaylist.fromJson(
 | 
			
		||||
              json['playlist'] as Map<String, dynamic>),
 | 
			
		||||
      artist: json['artist'] == null
 | 
			
		||||
          ? null
 | 
			
		||||
          : SpotifySectionArtist.fromJson(
 | 
			
		||||
              json['artist'] as Map<String, dynamic>),
 | 
			
		||||
      album: json['album'] == null
 | 
			
		||||
          ? null
 | 
			
		||||
          : SpotifySectionAlbum.fromJson(json['album'] as Map<String, dynamic>),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifyHomeFeedSectionItemImplToJson(
 | 
			
		||||
        _$SpotifyHomeFeedSectionItemImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'typename': instance.typename,
 | 
			
		||||
      'playlist': instance.playlist,
 | 
			
		||||
      'artist': instance.artist,
 | 
			
		||||
      'album': instance.album,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifyHomeFeedSectionImpl _$$SpotifyHomeFeedSectionImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifyHomeFeedSectionImpl(
 | 
			
		||||
      typename: json['typename'] as String,
 | 
			
		||||
      title: json['title'] as String?,
 | 
			
		||||
      uri: json['uri'] as String,
 | 
			
		||||
      items: (json['items'] as List<dynamic>)
 | 
			
		||||
          .map((e) =>
 | 
			
		||||
              SpotifyHomeFeedSectionItem.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifyHomeFeedSectionImplToJson(
 | 
			
		||||
        _$SpotifyHomeFeedSectionImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'typename': instance.typename,
 | 
			
		||||
      'title': instance.title,
 | 
			
		||||
      'uri': instance.uri,
 | 
			
		||||
      'items': instance.items,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
_$SpotifyHomeFeedImpl _$$SpotifyHomeFeedImplFromJson(
 | 
			
		||||
        Map<String, dynamic> json) =>
 | 
			
		||||
    _$SpotifyHomeFeedImpl(
 | 
			
		||||
      greeting: json['greeting'] as String,
 | 
			
		||||
      sections: (json['sections'] as List<dynamic>)
 | 
			
		||||
          .map(
 | 
			
		||||
              (e) => SpotifyHomeFeedSection.fromJson(e as Map<String, dynamic>))
 | 
			
		||||
          .toList(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
Map<String, dynamic> _$$SpotifyHomeFeedImplToJson(
 | 
			
		||||
        _$SpotifyHomeFeedImpl instance) =>
 | 
			
		||||
    <String, dynamic>{
 | 
			
		||||
      'greeting': instance.greeting,
 | 
			
		||||
      'sections': instance.sections,
 | 
			
		||||
    };
 | 
			
		||||
@@ -44,7 +44,7 @@ class AlbumCard extends StatelessWidget {
 | 
			
		||||
          ],
 | 
			
		||||
        ).paddingSymmetric(horizontal: 8),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          if (onTap != null) return;
 | 
			
		||||
          if (onTap == null) return;
 | 
			
		||||
          onTap!();
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ class PlaylistCard extends StatelessWidget {
 | 
			
		||||
          ],
 | 
			
		||||
        ).paddingSymmetric(horizontal: 8),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          if (onTap != null) return;
 | 
			
		||||
          if (onTap == null) return;
 | 
			
		||||
          onTap!();
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -614,6 +614,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.0"
 | 
			
		||||
  freezed:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: freezed
 | 
			
		||||
      sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.5.7"
 | 
			
		||||
  freezed_annotation:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -759,7 +767,7 @@ packages:
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "4.9.0"
 | 
			
		||||
  json_serializable:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: json_serializable
 | 
			
		||||
      sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
 | 
			
		||||
@@ -1382,6 +1390,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.7.2"
 | 
			
		||||
  timezone:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: timezone
 | 
			
		||||
      sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.9.4"
 | 
			
		||||
  timing:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,6 @@ dependencies:
 | 
			
		||||
  flutter_secure_storage: ^9.2.2
 | 
			
		||||
  window_manager: ^0.4.2
 | 
			
		||||
  lrc: ^1.0.2
 | 
			
		||||
  json_serializable: ^6.8.0
 | 
			
		||||
  path: ^1.9.0
 | 
			
		||||
  path_provider: ^2.1.4
 | 
			
		||||
  sqlite3_flutter_libs: ^0.5.23
 | 
			
		||||
@@ -100,6 +99,7 @@ dependencies:
 | 
			
		||||
      ref: feat/cookies
 | 
			
		||||
      path: packages/desktop_webview_window
 | 
			
		||||
  flutter_inappwebview: ^6.0.0
 | 
			
		||||
  timezone: ^0.9.4
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
@@ -115,6 +115,8 @@ dev_dependencies:
 | 
			
		||||
  build_runner: ^2.4.12
 | 
			
		||||
  flutter_launcher_icons: ^0.13.1
 | 
			
		||||
  flutter_native_splash: ^2.4.1
 | 
			
		||||
  freezed: ^2.5.7
 | 
			
		||||
  json_serializable: ^6.8.0
 | 
			
		||||
 | 
			
		||||
# For information on the generic Dart part of this file, see the
 | 
			
		||||
# following page: https://dart.dev/tools/pub/pubspec
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user