feat: flag to hide spotify generated images with patterns
BIN
assets/patterns/black_white_visualized.jpg
Normal file
After Width: | Height: | Size: 336 KiB |
BIN
assets/patterns/brazil_carnival.jpg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
assets/patterns/cotton_balls.jpg
Normal file
After Width: | Height: | Size: 498 KiB |
BIN
assets/patterns/cute_worms.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
assets/patterns/flash_cross_axis.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/patterns/memphis_shapes.jpg
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
assets/patterns/oval_gloomy.jpg
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
assets/patterns/oval_sunny.jpg
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
assets/patterns/red_nimbuses.jpg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
assets/patterns/tree_bark.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
assets/patterns/vibrant_pentagons.jpg
Normal file
After Width: | Height: | Size: 167 KiB |
BIN
assets/patterns/wiring_pattern.jpg
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
assets/patterns/zigzags_gloomy.jpg
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
assets/patterns/zigzags_sunny.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
@ -35,6 +35,84 @@ class $AssetsLogosGen {
|
||||
List<AssetGenImage> get values => [songlinkTransparent, songlink];
|
||||
}
|
||||
|
||||
class $AssetsPatternsGen {
|
||||
const $AssetsPatternsGen();
|
||||
|
||||
/// File path: assets/patterns/black_white_visualized.jpg
|
||||
AssetGenImage get blackWhiteVisualized =>
|
||||
const AssetGenImage('assets/patterns/black_white_visualized.jpg');
|
||||
|
||||
/// File path: assets/patterns/brazil_carnival.jpg
|
||||
AssetGenImage get brazilCarnival =>
|
||||
const AssetGenImage('assets/patterns/brazil_carnival.jpg');
|
||||
|
||||
/// File path: assets/patterns/cotton_balls.jpg
|
||||
AssetGenImage get cottonBalls =>
|
||||
const AssetGenImage('assets/patterns/cotton_balls.jpg');
|
||||
|
||||
/// File path: assets/patterns/cute_worms.jpg
|
||||
AssetGenImage get cuteWorms =>
|
||||
const AssetGenImage('assets/patterns/cute_worms.jpg');
|
||||
|
||||
/// File path: assets/patterns/flash_cross_axis.jpg
|
||||
AssetGenImage get flashCrossAxis =>
|
||||
const AssetGenImage('assets/patterns/flash_cross_axis.jpg');
|
||||
|
||||
/// File path: assets/patterns/memphis_shapes.jpg
|
||||
AssetGenImage get memphisShapes =>
|
||||
const AssetGenImage('assets/patterns/memphis_shapes.jpg');
|
||||
|
||||
/// File path: assets/patterns/oval_gloomy.jpg
|
||||
AssetGenImage get ovalGloomy =>
|
||||
const AssetGenImage('assets/patterns/oval_gloomy.jpg');
|
||||
|
||||
/// File path: assets/patterns/oval_sunny.jpg
|
||||
AssetGenImage get ovalSunny =>
|
||||
const AssetGenImage('assets/patterns/oval_sunny.jpg');
|
||||
|
||||
/// File path: assets/patterns/red_nimbuses.jpg
|
||||
AssetGenImage get redNimbuses =>
|
||||
const AssetGenImage('assets/patterns/red_nimbuses.jpg');
|
||||
|
||||
/// File path: assets/patterns/tree_bark.jpg
|
||||
AssetGenImage get treeBark =>
|
||||
const AssetGenImage('assets/patterns/tree_bark.jpg');
|
||||
|
||||
/// File path: assets/patterns/vibrant_pentagons.jpg
|
||||
AssetGenImage get vibrantPentagons =>
|
||||
const AssetGenImage('assets/patterns/vibrant_pentagons.jpg');
|
||||
|
||||
/// File path: assets/patterns/wiring_pattern.jpg
|
||||
AssetGenImage get wiringPattern =>
|
||||
const AssetGenImage('assets/patterns/wiring_pattern.jpg');
|
||||
|
||||
/// File path: assets/patterns/zigzags_gloomy.jpg
|
||||
AssetGenImage get zigzagsGloomy =>
|
||||
const AssetGenImage('assets/patterns/zigzags_gloomy.jpg');
|
||||
|
||||
/// File path: assets/patterns/zigzags_sunny.jpg
|
||||
AssetGenImage get zigzagsSunny =>
|
||||
const AssetGenImage('assets/patterns/zigzags_sunny.jpg');
|
||||
|
||||
/// List of all assets
|
||||
List<AssetGenImage> get values => [
|
||||
blackWhiteVisualized,
|
||||
brazilCarnival,
|
||||
cottonBalls,
|
||||
cuteWorms,
|
||||
flashCrossAxis,
|
||||
memphisShapes,
|
||||
ovalGloomy,
|
||||
ovalSunny,
|
||||
redNimbuses,
|
||||
treeBark,
|
||||
vibrantPentagons,
|
||||
wiringPattern,
|
||||
zigzagsGloomy,
|
||||
zigzagsSunny
|
||||
];
|
||||
}
|
||||
|
||||
class $AssetsTutorialGen {
|
||||
const $AssetsTutorialGen();
|
||||
|
||||
@ -67,6 +145,7 @@ class Assets {
|
||||
static const AssetGenImage likedTracks =
|
||||
AssetGenImage('assets/liked-tracks.jpg');
|
||||
static const $AssetsLogosGen logos = $AssetsLogosGen();
|
||||
static const $AssetsPatternsGen patterns = $AssetsPatternsGen();
|
||||
static const AssetGenImage placeholder =
|
||||
AssetGenImage('assets/placeholder.png');
|
||||
static const AssetGenImage spotubeHeroBanner =
|
||||
|
@ -38,6 +38,11 @@ abstract class Env {
|
||||
@EnviedField(varName: "RELEASE_CHANNEL", defaultValue: "nightly")
|
||||
static final String _releaseChannel = _Env._releaseChannel;
|
||||
|
||||
@EnviedField(varName: "DISABLE_SPOTIFY_IMAGES", defaultValue: "0")
|
||||
static final int _disableSpotifyImages = _Env._disableSpotifyImages;
|
||||
|
||||
static bool get disableSpotifyImages => _disableSpotifyImages == 1;
|
||||
|
||||
static ReleaseChannel get releaseChannel => _releaseChannel == "stable"
|
||||
? ReleaseChannel.stable
|
||||
: ReleaseChannel.nightly;
|
||||
|
@ -11,14 +11,14 @@ class PlaybuttonCard extends StatelessWidget {
|
||||
final void Function()? onAddToQueuePressed;
|
||||
final String? description;
|
||||
|
||||
final String imageUrl;
|
||||
final String? imageUrl;
|
||||
final Widget? image;
|
||||
final bool isPlaying;
|
||||
final bool isLoading;
|
||||
final String title;
|
||||
final bool isOwner;
|
||||
|
||||
const PlaybuttonCard({
|
||||
required this.imageUrl,
|
||||
required this.isPlaying,
|
||||
required this.isLoading,
|
||||
required this.title,
|
||||
@ -27,8 +27,13 @@ class PlaybuttonCard extends StatelessWidget {
|
||||
this.onAddToQueuePressed,
|
||||
this.onTap,
|
||||
this.isOwner = false,
|
||||
this.imageUrl,
|
||||
this.image,
|
||||
super.key,
|
||||
});
|
||||
}) : assert(
|
||||
imageUrl != null || image != null,
|
||||
"imageUrl and image can't be null at the same time",
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -40,17 +45,27 @@ class PlaybuttonCard extends StatelessWidget {
|
||||
child: CardImage(
|
||||
image: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 150 * scale,
|
||||
height: 150 * scale,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(imageUrl),
|
||||
fit: BoxFit.cover,
|
||||
if (imageUrl != null)
|
||||
Container(
|
||||
width: 150 * scale,
|
||||
height: 150 * scale,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(imageUrl!),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
width: 150 * scale,
|
||||
height: 150 * scale,
|
||||
child: ClipRRect(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
child: image!,
|
||||
),
|
||||
),
|
||||
),
|
||||
StatedWidget.builder(
|
||||
builder: (context, states) {
|
||||
return Positioned(
|
||||
|
@ -11,14 +11,14 @@ class PlaybuttonTile extends StatelessWidget {
|
||||
final void Function()? onAddToQueuePressed;
|
||||
final String? description;
|
||||
|
||||
final String imageUrl;
|
||||
final String? imageUrl;
|
||||
final Widget? image;
|
||||
final bool isPlaying;
|
||||
final bool isLoading;
|
||||
final String title;
|
||||
final bool isOwner;
|
||||
|
||||
const PlaybuttonTile({
|
||||
required this.imageUrl,
|
||||
required this.isPlaying,
|
||||
required this.isLoading,
|
||||
required this.title,
|
||||
@ -27,8 +27,13 @@ class PlaybuttonTile extends StatelessWidget {
|
||||
this.onAddToQueuePressed,
|
||||
this.onTap,
|
||||
this.isOwner = false,
|
||||
this.imageUrl,
|
||||
this.image,
|
||||
super.key,
|
||||
});
|
||||
}) : assert(
|
||||
imageUrl != null || image != null,
|
||||
"imageUrl and image can't be null at the same time",
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -36,17 +41,26 @@ class PlaybuttonTile extends StatelessWidget {
|
||||
final scale = context.theme.scaling;
|
||||
|
||||
return Button(
|
||||
leading: Container(
|
||||
width: 50 * scale,
|
||||
height: 50 * scale,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(imageUrl),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: imageUrl != null
|
||||
? Container(
|
||||
width: 50 * scale,
|
||||
height: 50 * scale,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(imageUrl!),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 50 * scale,
|
||||
height: 50 * scale,
|
||||
child: ClipRRect(
|
||||
borderRadius: context.theme.borderRadiusMd,
|
||||
child: image,
|
||||
),
|
||||
),
|
||||
style: ButtonVariance.ghost.copyWith(
|
||||
padding: (context, states, value) {
|
||||
return (ButtonVariance.ghost.padding(context, states) as EdgeInsets)
|
||||
|
@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/env.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/heart_button/heart_button.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
@ -12,6 +14,7 @@ import 'package:spotube/components/track_presentation/use_is_user_playlist.dart'
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/modules/playlist/playlist_create_dialog.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
|
||||
class TrackPresentationTopSection extends HookConsumerWidget {
|
||||
const TrackPresentationTopSection({super.key});
|
||||
@ -23,6 +26,26 @@ class TrackPresentationTopSection extends HookConsumerWidget {
|
||||
final scale = context.theme.scaling;
|
||||
final isUserPlaylist = useIsUserPlaylist(ref, options.collectionId);
|
||||
|
||||
final playlistImage = (options.collection is PlaylistSimple &&
|
||||
(options.collection as PlaylistSimple).owner?.displayName ==
|
||||
"Spotify" &&
|
||||
Env.disableSpotifyImages)
|
||||
? ref.watch(playlistImageProvider(options.collectionId))
|
||||
: null;
|
||||
final decorationImage = playlistImage != null
|
||||
? DecorationImage(
|
||||
image: AssetImage(playlistImage.src),
|
||||
fit: BoxFit.cover,
|
||||
colorFilter: ColorFilter.mode(
|
||||
playlistImage.color,
|
||||
playlistImage.colorBlendMode,
|
||||
),
|
||||
)
|
||||
: DecorationImage(
|
||||
image: UniversalImage.imageProvider(options.image),
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
|
||||
final imageDimension = mediaQuery.mdAndUp ? 200 : 120;
|
||||
|
||||
final (:isLoading, :isActive, :onPlay, :onShuffle) =
|
||||
@ -153,10 +176,7 @@ class TrackPresentationTopSection extends HookConsumerWidget {
|
||||
children: [
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: UniversalImage.imageProvider(options.image),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
image: decorationImage,
|
||||
borderRadius: BorderRadius.circular(45),
|
||||
),
|
||||
child: OutlinedContainer(
|
||||
@ -179,11 +199,7 @@ class TrackPresentationTopSection extends HookConsumerWidget {
|
||||
width: imageDimension * scale,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: context.theme.borderRadiusXl,
|
||||
image: DecorationImage(
|
||||
image:
|
||||
UniversalImage.imageProvider(options.image),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
image: decorationImage,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
|
@ -29,7 +29,7 @@ class SpotifySectionPlaylist with _$SpotifySectionPlaylist {
|
||||
..description = description
|
||||
..collaborative = false
|
||||
..images = images.map((e) => e.asImage).toList()
|
||||
..owner = (User()..displayName = "Spotify")
|
||||
..owner = (User()..displayName = owner)
|
||||
..uri = uri
|
||||
..type = "playlist";
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotify/spotify.dart' hide Image;
|
||||
import 'package:spotube/collections/env.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/extensions/string.dart';
|
||||
import 'package:spotube/pages/playlist/playlist.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:stroke_text/stroke_text.dart';
|
||||
|
||||
class GenreSectionCardPlaylistCard extends HookConsumerWidget {
|
||||
final PlaylistSimple playlist;
|
||||
@ -58,15 +61,58 @@ class GenreSectionCardPlaylistCard extends HookConsumerWidget {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: theme.borderRadiusSm,
|
||||
child: UniversalImage(
|
||||
path: (playlist.images)!.asUrlString(
|
||||
placeholder: ImagePlaceholder.collection,
|
||||
index: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
height: 100 * theme.scaling,
|
||||
width: 100 * theme.scaling,
|
||||
),
|
||||
child: playlist.owner?.displayName == "Spotify" &&
|
||||
Env.disableSpotifyImages
|
||||
? Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final (:src, :color, :colorBlendMode, :placement) =
|
||||
ref.watch(playlistImageProvider(playlist.id!));
|
||||
return SizedBox(
|
||||
height: 100 * theme.scaling,
|
||||
width: 100 * theme.scaling,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
src,
|
||||
color: color,
|
||||
colorBlendMode: colorBlendMode,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: placement == Alignment.topLeft
|
||||
? 10
|
||||
: null,
|
||||
left: 10,
|
||||
bottom: placement == Alignment.bottomLeft
|
||||
? 10
|
||||
: null,
|
||||
child: StrokeText(
|
||||
text: playlist.name!,
|
||||
strokeColor: Colors.white,
|
||||
strokeWidth: 3,
|
||||
textColor: Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: UniversalImage(
|
||||
path: (playlist.images)!.asUrlString(
|
||||
placeholder: ImagePlaceholder.collection,
|
||||
index: 1,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
height: 100 * theme.scaling,
|
||||
width: 100 * theme.scaling,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
playlist.name!,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotify/spotify.dart' hide Offset, Image;
|
||||
import 'package:spotube/collections/env.dart';
|
||||
import 'package:spotube/components/dialogs/select_device_dialog.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/components/playbutton_view/playbutton_card.dart';
|
||||
import 'package:spotube/components/playbutton_view/playbutton_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -16,6 +18,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||
import 'package:spotube/provider/spotify/spotify.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:stroke_text/stroke_text.dart';
|
||||
|
||||
class PlaylistCard extends HookConsumerWidget {
|
||||
final PlaylistSimple playlist;
|
||||
@ -168,11 +171,52 @@ class PlaylistCard extends HookConsumerWidget {
|
||||
final isOwner = playlist.owner?.id == me.asData?.value.id &&
|
||||
me.asData?.value.id != null;
|
||||
|
||||
final image =
|
||||
playlist.owner?.displayName == "Spotify" && Env.disableSpotifyImages
|
||||
? Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final (:color, :colorBlendMode, :src, :placement) =
|
||||
ref.watch(playlistImageProvider(playlist.id!));
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
src,
|
||||
color: color,
|
||||
colorBlendMode: colorBlendMode,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: placement == Alignment.topLeft ? 10 : null,
|
||||
left: 10,
|
||||
bottom: placement == Alignment.bottomLeft ? 10 : null,
|
||||
child: StrokeText(
|
||||
text: playlist.name!,
|
||||
strokeColor: Colors.white,
|
||||
strokeWidth: 3,
|
||||
textColor: Colors.black,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
: UniversalImage(
|
||||
path: imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
|
||||
if (_isTile) {
|
||||
return PlaybuttonTile(
|
||||
title: playlist.name!,
|
||||
description: playlist.description,
|
||||
imageUrl: imageUrl,
|
||||
image: image,
|
||||
isPlaying: isPlaylistPlaying,
|
||||
isLoading: isLoading,
|
||||
isOwner: isOwner,
|
||||
@ -185,7 +229,7 @@ class PlaylistCard extends HookConsumerWidget {
|
||||
return PlaybuttonCard(
|
||||
title: playlist.name!,
|
||||
description: playlist.description,
|
||||
imageUrl: imageUrl,
|
||||
image: image,
|
||||
isPlaying: isPlaylistPlaying,
|
||||
isLoading: isLoading,
|
||||
isOwner: isOwner,
|
||||
|
@ -104,3 +104,39 @@ final playlistProvider =
|
||||
AsyncNotifierProvider.family<PlaylistNotifier, Playlist, String>(
|
||||
() => PlaylistNotifier(),
|
||||
);
|
||||
|
||||
final _blendModes = BlendMode.values
|
||||
.where((e) => switch (e) {
|
||||
BlendMode.clear ||
|
||||
BlendMode.src ||
|
||||
BlendMode.srcATop ||
|
||||
BlendMode.srcIn ||
|
||||
BlendMode.srcOut ||
|
||||
BlendMode.srcOver ||
|
||||
BlendMode.dstOut ||
|
||||
BlendMode.xor =>
|
||||
false,
|
||||
_ => true
|
||||
})
|
||||
.toList();
|
||||
|
||||
typedef PlaylistImageInfo = ({
|
||||
Color color,
|
||||
BlendMode colorBlendMode,
|
||||
String src,
|
||||
Alignment placement,
|
||||
});
|
||||
|
||||
final playlistImageProvider = Provider.family<PlaylistImageInfo, String>(
|
||||
(ref, playlistId) {
|
||||
final random = Random();
|
||||
|
||||
return (
|
||||
color: Colors.primaries[random.nextInt(Colors.primaries.length)],
|
||||
colorBlendMode: _blendModes[random.nextInt(_blendModes.length)],
|
||||
src: Assets
|
||||
.patterns.values[random.nextInt(Assets.patterns.values.length)].path,
|
||||
placement: random.nextBool() ? Alignment.topLeft : Alignment.bottomLeft,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,8 +1,10 @@
|
||||
library spotify;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:spotube/collections/assets.gen.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/authentication/authentication.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
|
@ -172,6 +172,7 @@ flutter:
|
||||
- assets/tutorial/
|
||||
- assets/logos/
|
||||
- assets/backgrounds/
|
||||
- assets/patterns/
|
||||
- LICENSE
|
||||
fonts:
|
||||
- family: GeistSans
|
||||
|