feat(flutter): bump deps to latest + Search screen slice
Major version bumps (riverpod 2->3, go_router 14->17, just_audio 0.9->0.10, flutter_secure_storage 9->10, google_fonts 6->8, flutter_lints 4->6). package_info_plus held at 8.x due to win32 conflict with secure_storage 10.x. Riverpod 3 breaks: AsyncValue.valueOrNull -> .value, StateProvider replaced with Notifier subclass. just_audio 0.10: ConcatenatingAudioSource -> setAudioSources. Search slice: - models/page.dart (generic Page<T> envelope) - models/search_response.dart (3-facet wrapper) - api/endpoints/search.dart - search/search_provider.dart (debounced 250ms) - search/search_screen.dart (TextField + 3 horizontal/vertical sections) - Wired /search route + search button on home AppBar
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../models/search_response.dart';
|
||||
|
||||
/// SearchApi wraps GET /api/search. The server runs three facets (artists,
|
||||
/// albums, tracks) sharing one limit/offset pair. Each facet returns its
|
||||
/// own total reflecting the full match count.
|
||||
class SearchApi {
|
||||
SearchApi(this._dio);
|
||||
final Dio _dio;
|
||||
|
||||
/// Empty / whitespace-only query is the caller's responsibility to
|
||||
/// guard against — the server returns 400 bad_request for it.
|
||||
Future<SearchResponse> search(
|
||||
String query, {
|
||||
int limit = 20,
|
||||
int offset = 0,
|
||||
}) async {
|
||||
final r = await _dio.get<Map<String, dynamic>>(
|
||||
'/api/search',
|
||||
queryParameters: {
|
||||
'q': query,
|
||||
'limit': limit,
|
||||
'offset': offset,
|
||||
},
|
||||
);
|
||||
return SearchResponse.fromJson(r.data ?? const {});
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,18 @@ class HomeScreen extends ConsumerWidget {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
return Scaffold(
|
||||
backgroundColor: fs.obsidian,
|
||||
appBar: AppBar(
|
||||
backgroundColor: fs.obsidian,
|
||||
elevation: 0,
|
||||
title: Text('Minstrel', style: TextStyle(color: fs.parchment)),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.search, color: fs.parchment),
|
||||
tooltip: 'Search',
|
||||
onPressed: () => context.push('/search'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ref.watch(homeProvider).when(
|
||||
error: (e, _) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Mirrors the server's Page<T> envelope used by paged endpoints
|
||||
// (/api/search, /api/library/artists, /api/library/albums,
|
||||
// /api/library/history, /api/me/likes/*).
|
||||
//
|
||||
// Wire shape from internal/api/types.go Page[T]:
|
||||
// { items: T[], total: int, limit: int, offset: int }
|
||||
//
|
||||
// Dart generics can't auto-infer T from JSON, so callers pass an
|
||||
// itemFromJson function alongside the raw map. Parse logic stays in
|
||||
// each entity's own fromJson; this class only handles the envelope.
|
||||
class Page<T> {
|
||||
const Page({
|
||||
required this.items,
|
||||
required this.total,
|
||||
required this.limit,
|
||||
required this.offset,
|
||||
});
|
||||
|
||||
final List<T> items;
|
||||
final int total;
|
||||
final int limit;
|
||||
final int offset;
|
||||
|
||||
factory Page.fromJson(
|
||||
Map<String, dynamic> j,
|
||||
T Function(Map<String, dynamic>) itemFromJson,
|
||||
) {
|
||||
final raw = (j['items'] as List?) ?? const [];
|
||||
return Page<T>(
|
||||
items: raw
|
||||
.map((e) => itemFromJson((e as Map).cast<String, dynamic>()))
|
||||
.toList(growable: false),
|
||||
total: (j['total'] as num?)?.toInt() ?? 0,
|
||||
limit: (j['limit'] as num?)?.toInt() ?? 0,
|
||||
offset: (j['offset'] as num?)?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience for endpoints that always return the first page or
|
||||
/// when the caller just wants the items.
|
||||
bool get hasMore => offset + items.length < total;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'album.dart';
|
||||
import 'artist.dart';
|
||||
import 'page.dart';
|
||||
import 'track.dart';
|
||||
|
||||
/// Mirrors internal/api/search.go SearchResponse — three pages keyed by
|
||||
/// facet, each independently paged. The mobile UI usually only walks
|
||||
/// the first page of each facet (limit 20-50); infinite scroll within a
|
||||
/// facet is a future enhancement, not v1.
|
||||
class SearchResponse {
|
||||
const SearchResponse({
|
||||
required this.artists,
|
||||
required this.albums,
|
||||
required this.tracks,
|
||||
});
|
||||
|
||||
final Page<ArtistRef> artists;
|
||||
final Page<AlbumRef> albums;
|
||||
final Page<TrackRef> tracks;
|
||||
|
||||
factory SearchResponse.fromJson(Map<String, dynamic> j) => SearchResponse(
|
||||
artists: Page.fromJson(
|
||||
(j['artists'] as Map?)?.cast<String, dynamic>() ?? const {},
|
||||
ArtistRef.fromJson,
|
||||
),
|
||||
albums: Page.fromJson(
|
||||
(j['albums'] as Map?)?.cast<String, dynamic>() ?? const {},
|
||||
AlbumRef.fromJson,
|
||||
),
|
||||
tracks: Page.fromJson(
|
||||
(j['tracks'] as Map?)?.cast<String, dynamic>() ?? const {},
|
||||
TrackRef.fromJson,
|
||||
),
|
||||
);
|
||||
|
||||
bool get isEmpty =>
|
||||
artists.items.isEmpty && albums.items.isEmpty && tracks.items.isEmpty;
|
||||
}
|
||||
@@ -34,8 +34,8 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
|
||||
return AudioSource.uri(Uri.parse(url), headers: headers);
|
||||
}).toList();
|
||||
|
||||
await _player.setAudioSource(
|
||||
ConcatenatingAudioSource(children: sources),
|
||||
await _player.setAudioSources(
|
||||
sources,
|
||||
initialIndex: initialIndex,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ class NowPlayingScreen extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
final media = ref.watch(mediaItemProvider).valueOrNull;
|
||||
final playback = ref.watch(playbackStateProvider).valueOrNull;
|
||||
final media = ref.watch(mediaItemProvider).value;
|
||||
final playback = ref.watch(playbackStateProvider).value;
|
||||
|
||||
if (media == null) {
|
||||
return const Scaffold(body: Center(child: Text('Nothing playing.')));
|
||||
|
||||
@@ -11,8 +11,8 @@ class PlayerBar extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
final mediaItem = ref.watch(mediaItemProvider).valueOrNull;
|
||||
final playback = ref.watch(playbackStateProvider).valueOrNull;
|
||||
final mediaItem = ref.watch(mediaItemProvider).value;
|
||||
final playback = ref.watch(playbackStateProvider).value;
|
||||
if (mediaItem == null) return const SizedBox.shrink();
|
||||
|
||||
return Material(
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../api/endpoints/search.dart';
|
||||
import '../library/library_providers.dart' show dioProvider;
|
||||
import '../models/search_response.dart';
|
||||
|
||||
final searchApiProvider = FutureProvider<SearchApi>((ref) async {
|
||||
return SearchApi(await ref.watch(dioProvider.future));
|
||||
});
|
||||
|
||||
/// Current search query text. The screen's TextField writes here on
|
||||
/// every keystroke; the resultsProvider below debounces.
|
||||
class SearchQueryNotifier extends Notifier<String> {
|
||||
@override
|
||||
String build() => '';
|
||||
void set(String q) => state = q;
|
||||
}
|
||||
|
||||
final searchQueryProvider =
|
||||
NotifierProvider<SearchQueryNotifier, String>(SearchQueryNotifier.new);
|
||||
|
||||
/// Debounced search results. Returns null for empty queries so the
|
||||
/// screen can show a neutral empty state instead of "0 results."
|
||||
///
|
||||
/// Debounce: 250ms. After the wait, re-reads the live query — if the
|
||||
/// user has typed more in that window, this attempt is silently
|
||||
/// abandoned (returns null) and a newer attempt fires on the next
|
||||
/// rebuild. No request hits the server while the user is mid-typing.
|
||||
final searchResultsProvider = FutureProvider<SearchResponse?>((ref) async {
|
||||
final q = ref.watch(searchQueryProvider).trim();
|
||||
if (q.isEmpty) return null;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 250));
|
||||
// Re-read the (possibly newer) query; if the user typed more, bail.
|
||||
if (ref.read(searchQueryProvider).trim() != q) return null;
|
||||
final api = await ref.watch(searchApiProvider.future);
|
||||
return api.search(q);
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../library/widgets/album_card.dart';
|
||||
import '../library/widgets/artist_card.dart';
|
||||
import '../library/widgets/track_row.dart';
|
||||
import '../models/search_response.dart';
|
||||
import '../player/player_provider.dart';
|
||||
import '../theme/theme_extension.dart';
|
||||
import 'search_provider.dart';
|
||||
|
||||
class SearchScreen extends ConsumerStatefulWidget {
|
||||
const SearchScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<SearchScreen> createState() => _SearchScreenState();
|
||||
}
|
||||
|
||||
class _SearchScreenState extends ConsumerState<SearchScreen> {
|
||||
final _controller = TextEditingController();
|
||||
final _focus = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Autofocus the field on screen entry; keyboard pops up so the
|
||||
// user can start typing immediately.
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _focus.requestFocus());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
final results = ref.watch(searchResultsProvider);
|
||||
return Scaffold(
|
||||
backgroundColor: fs.obsidian,
|
||||
appBar: AppBar(
|
||||
backgroundColor: fs.obsidian,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: fs.parchment),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
autofocus: true,
|
||||
style: TextStyle(color: fs.parchment),
|
||||
cursorColor: fs.accent,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search artists, albums, tracks',
|
||||
hintStyle: TextStyle(color: fs.ash),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (v) => ref.read(searchQueryProvider.notifier).set(v),
|
||||
textInputAction: TextInputAction.search,
|
||||
),
|
||||
actions: [
|
||||
if (_controller.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: Icon(Icons.clear, color: fs.ash),
|
||||
onPressed: () {
|
||||
_controller.clear();
|
||||
ref.read(searchQueryProvider.notifier).set('');
|
||||
_focus.requestFocus();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: results.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text('$e', style: TextStyle(color: fs.error)),
|
||||
),
|
||||
),
|
||||
data: (r) => r == null
|
||||
? const _Hint(message: 'Type to search your library.')
|
||||
: r.isEmpty
|
||||
? const _Hint(message: 'No matches.')
|
||||
: _Results(results: r),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Hint extends StatelessWidget {
|
||||
const _Hint({required this.message});
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
return Center(
|
||||
child: Text(message, style: TextStyle(color: fs.ash)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Results extends ConsumerWidget {
|
||||
const _Results({required this.results});
|
||||
final SearchResponse results;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ListView(
|
||||
children: [
|
||||
if (results.artists.items.isNotEmpty) ...[
|
||||
_Header(label: 'Artists', count: results.artists.total),
|
||||
SizedBox(
|
||||
height: 168,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemCount: results.artists.items.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final a = results.artists.items[i];
|
||||
return ArtistCard(
|
||||
artist: a,
|
||||
onTap: () => ctx.push('/artists/${a.id}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
if (results.albums.items.isNotEmpty) ...[
|
||||
_Header(label: 'Albums', count: results.albums.total),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemCount: results.albums.items.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final a = results.albums.items[i];
|
||||
return AlbumCard(
|
||||
album: a,
|
||||
onTap: () => ctx.push('/albums/${a.id}'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
if (results.tracks.items.isNotEmpty) ...[
|
||||
_Header(label: 'Tracks', count: results.tracks.total),
|
||||
...results.tracks.items.asMap().entries.map((e) {
|
||||
final i = e.key;
|
||||
final t = e.value;
|
||||
return TrackRow(
|
||||
track: t,
|
||||
onTap: () => ref
|
||||
.read(playerActionsProvider)
|
||||
.playTracks(results.tracks.items, initialIndex: i),
|
||||
);
|
||||
}),
|
||||
],
|
||||
const SizedBox(height: 96),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({required this.label, required this.count});
|
||||
final String label;
|
||||
final int count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: fs.parchment,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text('$count', style: TextStyle(color: fs.ash, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import '../library/artist_detail_screen.dart';
|
||||
import '../library/home_screen.dart';
|
||||
import '../player/now_playing_screen.dart';
|
||||
import '../player/player_bar.dart';
|
||||
import '../search/search_screen.dart';
|
||||
import 'widgets/version_gate.dart';
|
||||
|
||||
/// Exposed as a Provider so its single argument is a real `Ref` (the
|
||||
@@ -51,6 +52,7 @@ GoRouter buildRouter(Ref ref) {
|
||||
builder: (_, s) => AlbumDetailScreen(id: s.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(path: '/now-playing', builder: (_, __) => const NowPlayingScreen()),
|
||||
GoRoute(path: '/search', builder: (_, __) => const SearchScreen()),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
+208
-32
@@ -1,6 +1,22 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "93.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -65,6 +81,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -89,6 +113,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -170,66 +210,66 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "6.0.0"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "3.3.1"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||
sha256: "8b302d17096ba88f911b7eb317c71d5e691da60a259549f42b38c658d1776d87"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.4"
|
||||
version: "10.1.0"
|
||||
flutter_secure_storage_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_darwin
|
||||
sha256: "3af15a3cb2bf5b8b776832bd01776f8018766aece55623176e28b406481fb320"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||
sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
flutter_secure_storage_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_macos
|
||||
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.0.0"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "2.0.1"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||
sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "2.1.1"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "4.1.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -248,6 +288,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -260,18 +308,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
|
||||
sha256: "92d8cee7c57dff0a6c409c05597b460002434eccf7424a712283225b3962d03f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.8.1"
|
||||
version: "17.2.3"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055
|
||||
sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
version: "8.1.0"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -288,6 +336,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -296,6 +352,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -324,10 +388,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: just_audio
|
||||
sha256: f978d5b4ccea08f267dae0232ec5405c1b05d3f3cd63f82097ea46c015d5c09e
|
||||
sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.46"
|
||||
version: "0.10.5"
|
||||
just_audio_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -372,10 +436,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "6.1.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -432,6 +496,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.6"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -552,6 +624,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -572,10 +652,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
version: "3.2.1"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -584,11 +664,59 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.13"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -685,6 +813,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test
|
||||
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.30.0"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -693,6 +829,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.16"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -749,6 +893,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -757,6 +909,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -10,22 +10,24 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_riverpod: ^2.5.1
|
||||
flutter_riverpod: ^3.3.1
|
||||
dio: ^5.7.0
|
||||
just_audio: ^0.9.40
|
||||
just_audio: ^0.10.5
|
||||
audio_service: ^0.18.15
|
||||
flutter_secure_storage: ^9.2.2
|
||||
go_router: ^14.6.1
|
||||
flutter_secure_storage: ^10.1.0
|
||||
go_router: ^17.2.3
|
||||
flutter_svg: ^2.0.16
|
||||
google_fonts: ^6.2.1
|
||||
package_info_plus: ^8.0.0
|
||||
google_fonts: ^8.1.0
|
||||
# 10.x conflicts with flutter_secure_storage 10.x on win32. Hold at 8.3.1
|
||||
# until either lib bumps win32 to 6.x.
|
||||
package_info_plus: ^8.3.1
|
||||
pub_semver: ^2.1.4
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^4.0.0
|
||||
flutter_lints: ^6.0.0
|
||||
mocktail: ^1.0.4
|
||||
|
||||
flutter:
|
||||
|
||||
Reference in New Issue
Block a user