💄 Optimize on tracks
This commit is contained in:
@@ -33,7 +33,7 @@ final class TrackRepositoryProvider
|
|||||||
TrackRepository create() => TrackRepository();
|
TrackRepository create() => TrackRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$trackRepositoryHash() => r'606c68068cb2811a0982c950ba0f12d77cdf9d44';
|
String _$trackRepositoryHash() => r'6a8bb9f1b4f29de32d6ad75c311353c4007e139f';
|
||||||
|
|
||||||
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
abstract class _$TrackRepository extends $AsyncNotifier<void> {
|
||||||
FutureOr<void> build();
|
FutureOr<void> build();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:media_kit/media_kit.dart' as media_kit;
|
import 'package:media_kit/media_kit.dart' as media_kit;
|
||||||
import 'package:groovybox/data/db.dart' as db;
|
import 'package:groovybox/data/db.dart' as db;
|
||||||
import 'package:groovybox/logic/metadata_service.dart';
|
import 'package:groovybox/logic/metadata_service.dart';
|
||||||
@@ -59,9 +59,17 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
TrackMetadata? metadata;
|
TrackMetadata? metadata;
|
||||||
db.Track? track;
|
db.Track? track;
|
||||||
|
|
||||||
// For remote tracks, get metadata from database
|
// Set loading state for remote tracks
|
||||||
final urlResolver = _container!.read(remoteUrlResolverProvider);
|
final urlResolver = _container!.read(remoteUrlResolverProvider);
|
||||||
if (urlResolver.isProtocolUrl(mediaItem.id)) {
|
final isRemoteTrack = urlResolver.isProtocolUrl(mediaItem.id);
|
||||||
|
|
||||||
|
final loadingNotifier = _container!.read(
|
||||||
|
remoteTrackLoadingProvider.notifier,
|
||||||
|
);
|
||||||
|
loadingNotifier.setLoading(true);
|
||||||
|
|
||||||
|
// For remote tracks, get metadata from database
|
||||||
|
if (isRemoteTrack) {
|
||||||
final database = _container!.read(databaseProvider);
|
final database = _container!.read(databaseProvider);
|
||||||
track = await (database.select(
|
track = await (database.select(
|
||||||
database.tracks,
|
database.tracks,
|
||||||
@@ -71,14 +79,10 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
// Fetch album art bytes for remote tracks
|
// Fetch album art bytes for remote tracks
|
||||||
Uint8List? artBytes;
|
Uint8List? artBytes;
|
||||||
if (track.artUri != null) {
|
if (track.artUri != null) {
|
||||||
try {
|
final imageFile = await DefaultCacheManager().getSingleFile(
|
||||||
final response = await http.get(Uri.parse(track.artUri!));
|
track.artUri!,
|
||||||
if (response.statusCode == 200) {
|
);
|
||||||
artBytes = response.bodyBytes;
|
artBytes = await imageFile.readAsBytes();
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore art fetching errors
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata = TrackMetadata(
|
metadata = TrackMetadata(
|
||||||
@@ -110,6 +114,9 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
seedColorNotifier.updateFromAlbumArtBytes(metadata.artBytes);
|
seedColorNotifier.updateFromAlbumArtBytes(metadata.artBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear loading state
|
||||||
|
loadingNotifier.setLoading(false);
|
||||||
|
|
||||||
// Set current track
|
// Set current track
|
||||||
final trackNotifier = _container!.read(currentTrackProvider.notifier);
|
final trackNotifier = _container!.read(currentTrackProvider.notifier);
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
@@ -128,6 +135,12 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
metadataNotifier.clear();
|
metadataNotifier.clear();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Clear loading state on error
|
||||||
|
final loadingNotifier = _container!.read(
|
||||||
|
remoteTrackLoadingProvider.notifier,
|
||||||
|
);
|
||||||
|
loadingNotifier.setLoading(false);
|
||||||
|
|
||||||
// If metadata retrieval fails, reset to default color and clear metadata/track
|
// If metadata retrieval fails, reset to default color and clear metadata/track
|
||||||
final seedColorNotifier = _container!.read(seedColorProvider.notifier);
|
final seedColorNotifier = _container!.read(seedColorProvider.notifier);
|
||||||
seedColorNotifier.resetToDefault();
|
seedColorNotifier.resetToDefault();
|
||||||
@@ -252,6 +265,12 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
final position = _player.state.position;
|
final position = _player.state.position;
|
||||||
final duration = _player.state.duration;
|
final duration = _player.state.duration;
|
||||||
|
|
||||||
|
// Get current media item metadata if available
|
||||||
|
MediaItem? currentMediaItem;
|
||||||
|
if (_queueIndex >= 0 && _queueIndex < _queue.length) {
|
||||||
|
currentMediaItem = _queue[_queueIndex];
|
||||||
|
}
|
||||||
|
|
||||||
playbackState.add(
|
playbackState.add(
|
||||||
PlaybackState(
|
PlaybackState(
|
||||||
controls: [
|
controls: [
|
||||||
@@ -274,21 +293,48 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
queueIndex: _queueIndex,
|
queueIndex: _queueIndex,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update media item separately if we have current track info
|
||||||
|
if (currentMediaItem != null) {
|
||||||
|
mediaItem.add(currentMediaItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New methods that accept Track objects with proper metadata
|
// New methods that accept Track objects with proper metadata
|
||||||
Future<void> playTrack(db.Track track) async {
|
Future<void> playTrack(db.Track track) async {
|
||||||
final mediaItem = _trackToMediaItem(track);
|
final mediaItem = await _trackToMediaItem(track);
|
||||||
await updateQueue([mediaItem]);
|
await updateQueue([mediaItem]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playTracks(List<db.Track> tracks, {int initialIndex = 0}) async {
|
Future<void> playTracks(List<db.Track> tracks, {int initialIndex = 0}) async {
|
||||||
final mediaItems = tracks.map(_trackToMediaItem).toList();
|
final mediaItems = await Future.wait(tracks.map(_trackToMediaItem));
|
||||||
_queueIndex = initialIndex;
|
_queueIndex = initialIndex;
|
||||||
await updateQueue(mediaItems);
|
await updateQueue(mediaItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaItem _trackToMediaItem(db.Track track) {
|
Future<MediaItem> _trackToMediaItem(db.Track track) async {
|
||||||
|
Uri? artUri;
|
||||||
|
|
||||||
|
if (track.artUri != null) {
|
||||||
|
// Check if it's a network URL or local file path
|
||||||
|
if (track.artUri!.startsWith('http://') ||
|
||||||
|
track.artUri!.startsWith('https://')) {
|
||||||
|
// It's a network URL, cache it and get local file path
|
||||||
|
try {
|
||||||
|
final cachedFile = await DefaultCacheManager().getSingleFile(
|
||||||
|
track.artUri!,
|
||||||
|
);
|
||||||
|
artUri = Uri.file(cachedFile.path);
|
||||||
|
} catch (e) {
|
||||||
|
// If caching fails, try to use the network URL directly
|
||||||
|
artUri = Uri.parse(track.artUri!);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's a local file path
|
||||||
|
artUri = Uri.file(track.artUri!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MediaItem(
|
return MediaItem(
|
||||||
id: track.path,
|
id: track.path,
|
||||||
album: track.album,
|
album: track.album,
|
||||||
@@ -297,21 +343,10 @@ class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
|||||||
duration: track.duration != null
|
duration: track.duration != null
|
||||||
? Duration(milliseconds: track.duration!)
|
? Duration(milliseconds: track.duration!)
|
||||||
: null,
|
: null,
|
||||||
artUri: track.artUri != null ? Uri.file(track.artUri!) : null,
|
artUri: artUri,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy methods for backward compatibility
|
|
||||||
Future<void> setSource(String path) async {
|
|
||||||
final mediaItem = MediaItem(
|
|
||||||
id: path,
|
|
||||||
album: 'Unknown Album',
|
|
||||||
title: _extractTitleFromPath(path),
|
|
||||||
artist: 'Unknown Artist',
|
|
||||||
);
|
|
||||||
await updateQueue([mediaItem]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> openPlaylist(
|
Future<void> openPlaylist(
|
||||||
List<media_kit.Media> medias, {
|
List<media_kit.Media> medias, {
|
||||||
int initialIndex = 0,
|
int initialIndex = 0,
|
||||||
|
|||||||
@@ -102,3 +102,15 @@ class CurrentTrackMetadataNotifier extends _$CurrentTrackMetadataNotifier {
|
|||||||
state = null;
|
state = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
class RemoteTrackLoadingNotifier extends _$RemoteTrackLoadingNotifier {
|
||||||
|
@override
|
||||||
|
bool build() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLoading(bool loading) {
|
||||||
|
state = loading;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,3 +158,57 @@ abstract class _$CurrentTrackMetadataNotifier
|
|||||||
element.handleValue(ref, created);
|
element.handleValue(ref, created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ProviderFor(RemoteTrackLoadingNotifier)
|
||||||
|
const remoteTrackLoadingProvider = RemoteTrackLoadingNotifierProvider._();
|
||||||
|
|
||||||
|
final class RemoteTrackLoadingNotifierProvider
|
||||||
|
extends $NotifierProvider<RemoteTrackLoadingNotifier, bool> {
|
||||||
|
const RemoteTrackLoadingNotifierProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'remoteTrackLoadingProvider',
|
||||||
|
isAutoDispose: false,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$remoteTrackLoadingNotifierHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
RemoteTrackLoadingNotifier create() => RemoteTrackLoadingNotifier();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(bool value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<bool>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$remoteTrackLoadingNotifierHash() =>
|
||||||
|
r'e7eda5cbbf3c37e0127960bbc09b121ab3b02afa';
|
||||||
|
|
||||||
|
abstract class _$RemoteTrackLoadingNotifier extends $Notifier<bool> {
|
||||||
|
bool build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<bool, bool>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<bool, bool>,
|
||||||
|
bool,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ final class LyricsFetcherProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$lyricsFetcherHash() => r'071b83cb569812a6f90d42d7b7cf6954ac9631d7';
|
String _$lyricsFetcherHash() => r'0b279b6294947cc0460c9fa8a5ef3863e24677f9';
|
||||||
|
|
||||||
abstract class _$LyricsFetcher extends $Notifier<LyricsFetcherState> {
|
abstract class _$LyricsFetcher extends $Notifier<LyricsFetcherState> {
|
||||||
LyricsFetcherState build();
|
LyricsFetcherState build();
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
final devicePadding = MediaQuery.paddingOf(context);
|
final devicePadding = MediaQuery.paddingOf(context);
|
||||||
|
|
||||||
final currentMetadata = ref.watch(currentTrackMetadataProvider);
|
final currentMetadata = ref.watch(currentTrackMetadataProvider);
|
||||||
|
final isRemoteTrackLoading = ref.watch(remoteTrackLoadingProvider);
|
||||||
|
|
||||||
Widget content = Container(
|
Widget content = Container(
|
||||||
height: 72 + devicePadding.bottom,
|
height: 72 + devicePadding.bottom,
|
||||||
@@ -75,53 +76,66 @@ class _MobileMiniPlayer extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 4,
|
height: 4,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: StreamBuilder<Duration>(
|
child: isRemoteTrackLoading
|
||||||
stream: player.stream.position,
|
? LinearProgressIndicator(
|
||||||
initialData: player.state.position,
|
backgroundColor: Theme.of(
|
||||||
builder: (context, snapshot) {
|
context,
|
||||||
final position = snapshot.data ?? Duration.zero;
|
).colorScheme.surfaceContainerHighest,
|
||||||
return StreamBuilder<Duration>(
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
stream: player.stream.duration,
|
Theme.of(context).colorScheme.primary,
|
||||||
initialData: player.state.duration,
|
),
|
||||||
builder: (context, durationSnapshot) {
|
)
|
||||||
final total = durationSnapshot.data ?? Duration.zero;
|
: StreamBuilder<Duration>(
|
||||||
final max = total.inMilliseconds.toDouble();
|
stream: player.stream.position,
|
||||||
final positionValue = position.inMilliseconds
|
initialData: player.state.position,
|
||||||
.toDouble()
|
builder: (context, snapshot) {
|
||||||
.clamp(0.0, max > 0 ? max : 0.0);
|
final position = snapshot.data ?? Duration.zero;
|
||||||
|
return StreamBuilder<Duration>(
|
||||||
|
stream: player.stream.duration,
|
||||||
|
initialData: player.state.duration,
|
||||||
|
builder: (context, durationSnapshot) {
|
||||||
|
final total =
|
||||||
|
durationSnapshot.data ?? Duration.zero;
|
||||||
|
final max = total.inMilliseconds.toDouble();
|
||||||
|
final positionValue = position.inMilliseconds
|
||||||
|
.toDouble()
|
||||||
|
.clamp(0.0, max > 0 ? max : 0.0);
|
||||||
|
|
||||||
final currentValue = isDragging.value
|
final currentValue = isDragging.value
|
||||||
? dragValue.value
|
? dragValue.value
|
||||||
: positionValue;
|
: positionValue;
|
||||||
|
|
||||||
return SliderTheme(
|
return SliderTheme(
|
||||||
data: SliderTheme.of(context).copyWith(
|
data: SliderTheme.of(context).copyWith(
|
||||||
trackHeight: 2,
|
trackHeight: 2,
|
||||||
overlayShape: SliderComponentShape.noOverlay,
|
overlayShape: SliderComponentShape.noOverlay,
|
||||||
thumbShape: const RoundSliderThumbShape(
|
thumbShape: const RoundSliderThumbShape(
|
||||||
enabledThumbRadius: 6,
|
enabledThumbRadius: 6,
|
||||||
),
|
),
|
||||||
trackShape: const RectangularSliderTrackShape(),
|
trackShape:
|
||||||
),
|
const RectangularSliderTrackShape(),
|
||||||
child: Slider(
|
),
|
||||||
padding: EdgeInsets.zero,
|
child: Slider(
|
||||||
value: currentValue,
|
padding: EdgeInsets.zero,
|
||||||
min: 0,
|
value: currentValue,
|
||||||
max: max > 0 ? max : 1.0,
|
min: 0,
|
||||||
onChanged: (val) {
|
max: max > 0 ? max : 1.0,
|
||||||
isDragging.value = true;
|
onChanged: (val) {
|
||||||
dragValue.value = val;
|
isDragging.value = true;
|
||||||
|
dragValue.value = val;
|
||||||
|
},
|
||||||
|
onChangeEnd: (val) {
|
||||||
|
isDragging.value = false;
|
||||||
|
player.seek(
|
||||||
|
Duration(milliseconds: val.toInt()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onChangeEnd: (val) {
|
);
|
||||||
isDragging.value = false;
|
},
|
||||||
player.seek(Duration(milliseconds: val.toInt()));
|
),
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -260,6 +274,7 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
|
|||||||
final devicePadding = MediaQuery.paddingOf(context);
|
final devicePadding = MediaQuery.paddingOf(context);
|
||||||
|
|
||||||
final currentMetadata = ref.watch(currentTrackMetadataProvider);
|
final currentMetadata = ref.watch(currentTrackMetadataProvider);
|
||||||
|
final isRemoteTrackLoading = ref.watch(remoteTrackLoadingProvider);
|
||||||
|
|
||||||
Widget content = Container(
|
Widget content = Container(
|
||||||
height: 72 + devicePadding.bottom,
|
height: 72 + devicePadding.bottom,
|
||||||
@@ -281,53 +296,66 @@ class _DesktopMiniPlayer extends HookConsumerWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 4,
|
height: 4,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: StreamBuilder<Duration>(
|
child: isRemoteTrackLoading
|
||||||
stream: player.stream.position,
|
? LinearProgressIndicator(
|
||||||
initialData: player.state.position,
|
backgroundColor: Theme.of(
|
||||||
builder: (context, snapshot) {
|
context,
|
||||||
final position = snapshot.data ?? Duration.zero;
|
).colorScheme.surfaceContainerHighest,
|
||||||
return StreamBuilder<Duration>(
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
stream: player.stream.duration,
|
Theme.of(context).colorScheme.primary,
|
||||||
initialData: player.state.duration,
|
),
|
||||||
builder: (context, durationSnapshot) {
|
)
|
||||||
final total = durationSnapshot.data ?? Duration.zero;
|
: StreamBuilder<Duration>(
|
||||||
final max = total.inMilliseconds.toDouble();
|
stream: player.stream.position,
|
||||||
final positionValue = position.inMilliseconds
|
initialData: player.state.position,
|
||||||
.toDouble()
|
builder: (context, snapshot) {
|
||||||
.clamp(0.0, max > 0 ? max : 0.0);
|
final position = snapshot.data ?? Duration.zero;
|
||||||
|
return StreamBuilder<Duration>(
|
||||||
|
stream: player.stream.duration,
|
||||||
|
initialData: player.state.duration,
|
||||||
|
builder: (context, durationSnapshot) {
|
||||||
|
final total =
|
||||||
|
durationSnapshot.data ?? Duration.zero;
|
||||||
|
final max = total.inMilliseconds.toDouble();
|
||||||
|
final positionValue = position.inMilliseconds
|
||||||
|
.toDouble()
|
||||||
|
.clamp(0.0, max > 0 ? max : 0.0);
|
||||||
|
|
||||||
final currentValue = isDragging.value
|
final currentValue = isDragging.value
|
||||||
? dragValue.value
|
? dragValue.value
|
||||||
: positionValue;
|
: positionValue;
|
||||||
|
|
||||||
return SliderTheme(
|
return SliderTheme(
|
||||||
data: SliderTheme.of(context).copyWith(
|
data: SliderTheme.of(context).copyWith(
|
||||||
trackHeight: 2,
|
trackHeight: 2,
|
||||||
overlayShape: SliderComponentShape.noOverlay,
|
overlayShape: SliderComponentShape.noOverlay,
|
||||||
thumbShape: const RoundSliderThumbShape(
|
thumbShape: const RoundSliderThumbShape(
|
||||||
enabledThumbRadius: 6,
|
enabledThumbRadius: 6,
|
||||||
),
|
),
|
||||||
trackShape: const RectangularSliderTrackShape(),
|
trackShape:
|
||||||
),
|
const RectangularSliderTrackShape(),
|
||||||
child: Slider(
|
),
|
||||||
padding: EdgeInsets.zero,
|
child: Slider(
|
||||||
value: currentValue,
|
padding: EdgeInsets.zero,
|
||||||
min: 0,
|
value: currentValue,
|
||||||
max: max > 0 ? max : 1.0,
|
min: 0,
|
||||||
onChanged: (val) {
|
max: max > 0 ? max : 1.0,
|
||||||
isDragging.value = true;
|
onChanged: (val) {
|
||||||
dragValue.value = val;
|
isDragging.value = true;
|
||||||
|
dragValue.value = val;
|
||||||
|
},
|
||||||
|
onChangeEnd: (val) {
|
||||||
|
isDragging.value = false;
|
||||||
|
player.seek(
|
||||||
|
Duration(milliseconds: val.toInt()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onChangeEnd: (val) {
|
);
|
||||||
isDragging.value = false;
|
},
|
||||||
player.seek(Duration(milliseconds: val.toInt()));
|
),
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -413,10 +413,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "7872545770c277236fd32b022767576c562ba28366204ff1a5628853cf8f2200"
|
sha256: d974b6ba2606371ac71dd94254beefb6fa81185bde0b59bdc1df09885da85fde
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.7"
|
version: "10.3.8"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -431,7 +431,7 @@ packages:
|
|||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_cache_manager:
|
flutter_cache_manager:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_cache_manager
|
name: flutter_cache_manager
|
||||||
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||||
@@ -588,10 +588,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "51555e36056541237b15b57afc31a0f53d4f9aefd9bd00873a6dc0090e54e332"
|
sha256: "48c11d0943b93b6fb29103d956ff89aafeae48f6058a3939649be2093dcff0bf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.0"
|
version: "4.7.1"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1281,10 +1281,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uri_parser
|
name: uri_parser
|
||||||
sha256: "380c6c96c52a8de82c84c627d0a79cfb2e1697c3fe3eda78fbb9b87af0dcee08"
|
sha256: "051c62e5f693de98ca9f130ee707f8916e2266945565926be3ff20659f7853ce"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ dependencies:
|
|||||||
shared_preferences: ^2.3.5
|
shared_preferences: ^2.3.5
|
||||||
jellyfin_dart: ^0.1.2
|
jellyfin_dart: ^0.1.2
|
||||||
cached_network_image: ^3.4.1
|
cached_network_image: ^3.4.1
|
||||||
|
flutter_cache_manager: ^3.4.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user