fix(flutter): player bar layout, system playlist tap, banner height

Player bar: drop the volume slider — phone hardware controls volume.
Right cluster collapses to a single row of shuffle/repeat/queue.

Playlist card: GestureDetector defers to children by default, so taps
over the cover image area didn't always register. Add HitTestBehavior.opaque
so the whole card surface is tappable.

Update banner: was 3-4× the height of its text — IconButton's default
48dp tap target plus generous vertical padding dominated. Tighten
padding (8→4), shrink dismiss button to 32×32 with iconSize 16, slim
the action button to 28dp height with shrinkWrap tap target.

Home screen: ClampingScrollPhysics + 140dp bottom padding (already
applied in working tree) so scroll stops just above the mini-player.
This commit is contained in:
2026-05-11 07:27:14 -04:00
parent 51afc992d4
commit f65650fb6d
5 changed files with 395 additions and 211 deletions
+24 -14
View File
@@ -58,20 +58,30 @@ class HomeScreen extends ConsumerWidget {
),
data: (h) => RefreshIndicator(
onRefresh: () async => ref.refresh(homeProvider.future),
child: ListView(children: [
_PlaylistsSection(
playlists: allPlaylists.value?.owned ?? const [],
status: status.value ?? SystemPlaylistsStatus.empty(),
),
_RecentlyAddedSection(albums: h.recentlyAddedAlbums),
_RediscoverSection(
albums: h.rediscoverAlbums,
artists: h.rediscoverArtists,
),
_MostPlayedSection(tracks: h.mostPlayedTracks),
_LastPlayedSection(artists: h.lastPlayedArtists),
const SizedBox(height: 96),
]),
child: ListView(
// ClampingScrollPhysics: no bounce overscroll past the
// content. Combined with the bottom padding below, this
// makes "scroll to end" land the last item just above the
// player bar — no empty void.
physics: const ClampingScrollPhysics(),
children: [
_PlaylistsSection(
playlists: allPlaylists.value?.owned ?? const [],
status: status.value ?? SystemPlaylistsStatus.empty(),
),
_RecentlyAddedSection(albums: h.recentlyAddedAlbums),
_RediscoverSection(
albums: h.rediscoverAlbums,
artists: h.rediscoverArtists,
),
_MostPlayedSection(tracks: h.mostPlayedTracks),
_LastPlayedSection(artists: h.lastPlayedArtists),
// Bottom padding ≈ player bar height (top row 80 + seek
// 30 + padding 16 ≈ ~140) so the last section's bottom
// edge lands right above the player when scrolled.
const SizedBox(height: 140),
],
),
),
),
),
+130 -181
View File
@@ -10,19 +10,20 @@ import '../shared/widgets/track_actions/track_actions_button.dart';
import '../theme/theme_extension.dart';
import 'player_provider.dart';
/// Compact player bar mounted in the app shell. Layout mirrors the
/// web's compact PlayerBar (#358):
/// Compact player bar mounted in the app shell. Layout:
///
/// ┌─────────────────────┬─────────────────┐
/// │ art │ ♥ ⋮ │ 🔀 🔁 ☰ │ ← short row
/// │ title
/// │ artist │ ⏮ ⏯ ⏭ │ volume slider │ ← play controls
/// ├──────────┴───────────┴──────────────────┤
/// │ 0:42 ━━━━━●━━━━━━━━━━━━━━━━━━━━━ 3:21 │
/// └─────────────────────────────────────────┘
/// ┌────────────────────────────┬─────────────────┐
/// │ [art] Title ♥ ⋮ │ ⏮ ⏯ ⏭ │ 🔀 🔁 ☰ │
/// │ Artist
/// ├────────────────────────────┴────────┴─────────┤
/// │ 0:00 ━━●━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3:06 │
/// └────────────────────────────────────────────────┘
///
/// Track-info column (cover + title block) tap target opens
/// NowPlayingScreen.
/// Heart + kebab sit inline at the right of the title (per operator
/// "directly to the right of the track name"). Play controls stay
/// full-size (40/48/40). Right column: shuffle/repeat/queue. Volume
/// is handled by the phone's hardware buttons. Bottom: full-width
/// seek + time labels.
class PlayerBar extends ConsumerWidget {
const PlayerBar({super.key});
@@ -43,21 +44,16 @@ class PlayerBar extends ConsumerWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Top row: track info (with inline like+kebab) | play controls | right cluster
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
flex: 35,
child: _TrackInfoColumn(media: media),
),
Expanded(child: _TrackInfoColumn(media: media)),
const SizedBox(width: 8),
_MiddleColumn(media: media, playback: playback, ref: ref),
_PlayControls(playback: playback, ref: ref),
const SizedBox(width: 8),
Expanded(
flex: 30,
child: _RightColumn(playback: playback),
),
_RightCluster(playback: playback),
],
),
),
@@ -77,6 +73,8 @@ class _TrackInfoColumn extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
final artistName = (media.artist ?? '').trim();
return InkWell(
onTap: () => context.push('/now-playing'),
child: Row(
@@ -99,18 +97,33 @@ class _TrackInfoColumn extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
media.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: fs.parchment, fontSize: 14),
),
Text(
media.artist ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: fs.ash, fontSize: 12),
// Title row: title text + inline like + kebab
Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
media.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: fs.parchment, fontSize: 14),
),
),
LikeButton(kind: LikeKind.track, id: media.id, size: 18),
TrackActionsButton(
track: _trackRefFromMediaItem(media),
hideQueueActions: true,
),
],
),
if (artistName.isNotEmpty)
Text(
artistName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: fs.ash, fontSize: 12),
),
],
),
),
@@ -120,13 +133,8 @@ class _TrackInfoColumn extends StatelessWidget {
}
}
class _MiddleColumn extends StatelessWidget {
const _MiddleColumn({
required this.media,
required this.playback,
required this.ref,
});
final MediaItem media;
class _PlayControls extends StatelessWidget {
const _PlayControls({required this.playback, required this.ref});
final PlaybackState? playback;
final WidgetRef ref;
@@ -134,73 +142,54 @@ class _MiddleColumn extends StatelessWidget {
Widget build(BuildContext context) {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
final isPlaying = playback?.playing == true;
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Top sub-row: like + kebab (shorter than play controls)
Row(
mainAxisSize: MainAxisSize.min,
children: [
LikeButton(kind: LikeKind.track, id: media.id, size: 18),
TrackActionsButton(
track: _trackRefFromMediaItem(media),
hideQueueActions: true,
),
],
SizedBox(
width: 40,
height: 40,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.skip_previous, color: fs.parchment, size: 22),
onPressed: () => ref.read(audioHandlerProvider).skipToPrevious(),
),
),
// Bottom sub-row: play controls — full size 40 / 48 / 40
Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.skip_previous, color: fs.parchment, size: 22),
onPressed: () =>
ref.read(audioHandlerProvider).skipToPrevious(),
),
SizedBox(
width: 48,
height: 48,
child: IconButton(
padding: EdgeInsets.zero,
iconSize: 28,
icon: Icon(
isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled,
color: fs.accent,
),
SizedBox(
width: 48,
height: 48,
child: IconButton(
padding: EdgeInsets.zero,
iconSize: 28,
icon: Icon(
isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled,
color: fs.accent,
),
onPressed: () {
final h = ref.read(audioHandlerProvider);
if (isPlaying) {
h.pause();
} else {
h.play();
}
},
),
),
SizedBox(
width: 40,
height: 40,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.skip_next, color: fs.parchment, size: 22),
onPressed: () => ref.read(audioHandlerProvider).skipToNext(),
),
),
],
onPressed: () {
final h = ref.read(audioHandlerProvider);
if (isPlaying) {
h.pause();
} else {
h.play();
}
},
),
),
SizedBox(
width: 40,
height: 40,
child: IconButton(
padding: EdgeInsets.zero,
icon: Icon(Icons.skip_next, color: fs.parchment, size: 22),
onPressed: () => ref.read(audioHandlerProvider).skipToNext(),
),
),
],
);
}
}
class _RightColumn extends ConsumerWidget {
const _RightColumn({required this.playback});
class _RightCluster extends ConsumerWidget {
const _RightCluster({required this.playback});
final PlaybackState? playback;
@override
@@ -208,96 +197,56 @@ class _RightColumn extends ConsumerWidget {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
final shuffleOn = playback?.shuffleMode == AudioServiceShuffleMode.all;
final repeatMode = playback?.repeatMode ?? AudioServiceRepeatMode.none;
final volume = ref.watch(volumeProvider).value ?? 1.0;
final actions = ref.read(playerActionsProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Top sub-row: shuffle / repeat / queue
Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: [
SizedBox(
width: 32,
height: 32,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: shuffleOn ? 'Shuffle on' : 'Shuffle off',
icon: Icon(
Icons.shuffle,
size: 18,
color: shuffleOn ? fs.accent : fs.ash,
),
onPressed: actions.toggleShuffle,
),
SizedBox(
width: 36,
height: 36,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: shuffleOn ? 'Shuffle on' : 'Shuffle off',
icon: Icon(
Icons.shuffle,
size: 18,
color: shuffleOn ? fs.accent : fs.ash,
),
SizedBox(
width: 32,
height: 32,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: switch (repeatMode) {
AudioServiceRepeatMode.none => 'Repeat off',
AudioServiceRepeatMode.one => 'Repeat one',
_ => 'Repeat all',
},
icon: Icon(
repeatMode == AudioServiceRepeatMode.one
? Icons.repeat_one
: Icons.repeat,
size: 18,
color: repeatMode == AudioServiceRepeatMode.none
? fs.ash
: fs.accent,
),
onPressed: actions.cycleRepeat,
),
),
SizedBox(
width: 32,
height: 32,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: 'Queue',
icon: Icon(Icons.queue_music, size: 18, color: fs.ash),
onPressed: () => context.push('/queue'),
),
),
],
onPressed: actions.toggleShuffle,
),
),
// Bottom sub-row: volume slider
Row(
children: [
Icon(
volume == 0
? Icons.volume_off
: (volume < 0.5 ? Icons.volume_down : Icons.volume_up),
size: 14,
color: fs.ash,
SizedBox(
width: 36,
height: 36,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: switch (repeatMode) {
AudioServiceRepeatMode.none => 'Repeat off',
AudioServiceRepeatMode.one => 'Repeat one',
_ => 'Repeat all',
},
icon: Icon(
repeatMode == AudioServiceRepeatMode.one
? Icons.repeat_one
: Icons.repeat,
size: 18,
color: repeatMode == AudioServiceRepeatMode.none
? fs.ash
: fs.accent,
),
const SizedBox(width: 4),
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 2,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6),
overlayShape:
const RoundSliderOverlayShape(overlayRadius: 12),
),
child: Slider(
activeColor: fs.accent,
inactiveColor: fs.slate,
min: 0,
max: 1,
value: volume.clamp(0.0, 1.0),
onChanged: actions.setVolume,
),
),
),
],
onPressed: actions.cycleRepeat,
),
),
SizedBox(
width: 36,
height: 36,
child: IconButton(
padding: EdgeInsets.zero,
tooltip: 'Queue',
icon: Icon(Icons.queue_music, size: 18, color: fs.ash),
onPressed: () => context.push('/queue'),
),
),
],
);
@@ -19,6 +19,7 @@ class PlaylistCard extends StatelessWidget {
return SizedBox(
width: 176,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => context.push('/playlists/${playlist.id}'),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
+31 -15
View File
@@ -62,11 +62,12 @@ class _UpdateBannerState extends ConsumerState<UpdateBanner> {
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 4, 8),
padding: const EdgeInsets.fromLTRB(12, 4, 4, 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.system_update, color: fs.parchment, size: 18),
const SizedBox(width: 10),
Icon(Icons.system_update, color: fs.parchment, size: 16),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -76,11 +77,11 @@ class _UpdateBannerState extends ConsumerState<UpdateBanner> {
_stage == _Stage.error
? 'Update failed'
: 'Update Minstrel · ${info.version} available',
style: TextStyle(color: fs.parchment, fontSize: 13),
style: TextStyle(color: fs.parchment, fontSize: 12),
overflow: TextOverflow.ellipsis,
),
if (_stage == _Stage.downloading) ...[
const SizedBox(height: 4),
const SizedBox(height: 2),
LinearProgressIndicator(
value: _progress > 0 ? _progress : null,
minHeight: 2,
@@ -89,11 +90,11 @@ class _UpdateBannerState extends ConsumerState<UpdateBanner> {
),
],
if (_stage == _Stage.error && _error != null) ...[
const SizedBox(height: 2),
const SizedBox(height: 1),
Text(
_error!,
style: TextStyle(color: fs.ash, fontSize: 11),
maxLines: 2,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
@@ -105,20 +106,35 @@ class _UpdateBannerState extends ConsumerState<UpdateBanner> {
onPressed: () => _onInstall(info),
style: TextButton.styleFrom(
foregroundColor: fs.accent,
minimumSize: const Size(64, 36),
minimumSize: const Size(56, 28),
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
child: const Text('Install'),
child: const Text('Install', style: TextStyle(fontSize: 13)),
),
if (_stage == _Stage.error)
TextButton(
onPressed: () => _onInstall(info),
style: TextButton.styleFrom(foregroundColor: fs.accent),
child: const Text('Retry'),
style: TextButton.styleFrom(
foregroundColor: fs.accent,
minimumSize: const Size(56, 28),
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
child: const Text('Retry', style: TextStyle(fontSize: 13)),
),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: 'Dismiss',
padding: EdgeInsets.zero,
iconSize: 16,
icon: Icon(Icons.close, color: fs.ash),
onPressed: () => _onDismiss(info),
),
IconButton(
tooltip: 'Dismiss',
icon: Icon(Icons.close, size: 18, color: fs.ash),
onPressed: () => _onDismiss(info),
),
],
),
+209 -1
View File
@@ -73,6 +73,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
build:
dependency: transitive
description:
name: build
sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
url: "https://pub.dev"
source: hosted
version: "4.0.6"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
url: "https://pub.dev"
source: hosted
version: "4.1.1"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
url: "https://pub.dev"
source: hosted
version: "2.15.0"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
url: "https://pub.dev"
source: hosted
version: "8.12.6"
characters:
dependency: transitive
description:
@@ -81,6 +129,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_config:
dependency: transitive
description:
@@ -89,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock:
dependency: transitive
description:
@@ -113,6 +185,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
url: "https://pub.dev"
source: hosted
version: "6.1.5"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
convert:
dependency: transitive
description:
@@ -145,6 +233,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.9"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
url: "https://pub.dev"
source: hosted
version: "3.1.7"
dbus:
dependency: transitive
description:
name: dbus
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
url: "https://pub.dev"
source: hosted
version: "0.7.12"
dio:
dependency: "direct main"
description:
@@ -161,6 +265,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
drift:
dependency: "direct main"
description:
name: drift
sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e"
url: "https://pub.dev"
source: hosted
version: "2.31.0"
drift_dev:
dependency: "direct dev"
description:
name: drift_dev
sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587"
url: "https://pub.dev"
source: hosted
version: "2.31.0"
drift_flutter:
dependency: "direct main"
description:
name: drift_flutter
sha256: c07120854742a0cae2f7501a0da02493addde550db6641d284983c08762e60a7
url: "https://pub.dev"
source: hosted
version: "0.2.8"
fake_async:
dependency: transitive
description:
@@ -320,6 +448,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.0"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
@@ -384,6 +520,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8
url: "https://pub.dev"
source: hosted
version: "4.11.0"
just_audio:
dependency: "direct main"
description:
@@ -496,6 +640,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.17.6"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble:
dependency: transitive
description:
@@ -553,7 +705,7 @@ packages:
source: hosted
version: "1.1.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
@@ -640,6 +792,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
recase:
dependency: transitive
description:
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev"
source: hosted
version: "4.1.0"
record_use:
dependency: transitive
description:
@@ -701,6 +869,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
url: "https://pub.dev"
source: hosted
version: "4.2.3"
source_map_stack_trace:
dependency: transitive
description:
@@ -765,6 +941,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
url: "https://pub.dev"
source: hosted
version: "2.9.4"
sqlite3_flutter_libs:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
url: "https://pub.dev"
source: hosted
version: "0.5.42"
sqlparser:
dependency: transitive
description:
name: sqlparser
sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19"
url: "https://pub.dev"
source: hosted
version: "0.43.1"
stack_trace:
dependency: transitive
description:
@@ -789,6 +989,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description: