From 5a2537e8c824dbd42c5cb2640206e5a826021bb4 Mon Sep 17 00:00:00 2001 From: Feichtmeier Date: Sat, 31 Aug 2024 12:45:24 +0200 Subject: [PATCH] fix: align control panel sizes and padding, correctly load remoteimageurl --- lib/common/view/icons.dart | 6 +- lib/common/view/sliver_audio_page.dart | 1 - lib/common/view/sliver_filter_app_bar.dart | 35 +++ lib/common/view/theme.dart | 14 +- lib/local_audio/view/artist_page.dart | 1 - .../view/local_audio_control_panel.dart | 7 +- lib/local_audio/view/local_audio_page.dart | 20 +- lib/player/player_service.dart | 3 +- lib/playlists/view/playlist_page.dart | 1 - .../podcast_collection_control_panel.dart | 6 +- lib/podcasts/view/podcast_page.dart | 1 - .../view/podcasts_collection_body.dart | 16 +- lib/radio/view/radio_lib_page.dart | 29 ++- lib/radio/view/station_page.dart | 1 - lib/search/view/search_page.dart | 19 +- .../view/sliver_podcast_filter_bar.dart | 228 ++++++++---------- .../view/sliver_search_type_filter_bar.dart | 100 ++++---- pubspec.lock | 4 +- 18 files changed, 261 insertions(+), 231 deletions(-) create mode 100644 lib/common/view/sliver_filter_app_bar.dart diff --git a/lib/common/view/icons.dart b/lib/common/view/icons.dart index c41bcf687..162c0e70b 100644 --- a/lib/common/view/icons.dart +++ b/lib/common/view/icons.dart @@ -375,10 +375,10 @@ class Iconz { ? CupertinoIcons.clear : Icons.clear; IconData get viewMore => yaruStyled - ? YaruIcons.view_more_horizontal + ? YaruIcons.view_more : appleStyled - ? CupertinoIcons.ellipsis - : Icons.more_horiz; + ? CupertinoIcons.ellipsis_vertical + : Icons.more_vert; IconData? get close => yaruStyled ? YaruIcons.window_close : appleStyled diff --git a/lib/common/view/sliver_audio_page.dart b/lib/common/view/sliver_audio_page.dart index d1092feb9..59532dc6b 100644 --- a/lib/common/view/sliver_audio_page.dart +++ b/lib/common/view/sliver_audio_page.dart @@ -61,7 +61,6 @@ class SliverAudioPage extends StatelessWidget { resizeToAvoidBottomInset: isMobile ? false : null, appBar: HeaderBar( adaptive: true, - title: isMobile ? null : Text(pageTitle ?? pageId), actions: [ Padding( padding: appBarSingleActionSpacing, diff --git a/lib/common/view/sliver_filter_app_bar.dart b/lib/common/view/sliver_filter_app_bar.dart new file mode 100644 index 000000000..b666c293c --- /dev/null +++ b/lib/common/view/sliver_filter_app_bar.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import '../../extensions/build_context_x.dart'; + +class SliverFilterAppBar extends StatelessWidget { + const SliverFilterAppBar({ + super.key, + this.onStretchTrigger, + required this.title, + required this.padding, + }); + + final Future Function()? onStretchTrigger; + final Widget title; + final EdgeInsets padding; + + @override + Widget build(BuildContext context) { + return SliverPadding( + padding: padding, + sliver: SliverAppBar( + shape: const RoundedRectangleBorder(side: BorderSide.none), + elevation: 0, + backgroundColor: context.t.scaffoldBackgroundColor, + automaticallyImplyLeading: false, + pinned: true, + centerTitle: true, + titleSpacing: 0, + stretch: true, + title: title, + onStretchTrigger: onStretchTrigger, + ), + ); + } +} diff --git a/lib/common/view/theme.dart b/lib/common/view/theme.dart index 5efc63232..51bdcd173 100644 --- a/lib/common/view/theme.dart +++ b/lib/common/view/theme.dart @@ -208,6 +208,9 @@ Color? chipBorder(ThemeData theme, bool loading) { return yaruStyled ? (loading ? null : Colors.transparent) : null; } +TextStyle chipTextStyle(ThemeData theme) => + TextStyle(color: theme.colorScheme.onSurface); + Color? chipSelectionColor(ThemeData theme, bool loading) { return yaruStyled ? (loading ? theme.colorScheme.outline : null) : null; } @@ -216,10 +219,19 @@ double get likeButtonWidth => yaruStyled ? 62 : 70; double get progressStrokeWidth => 3.0; -double get avatarIconRadius => (yaruStyled ? kYaruTitleBarItemHeight : 38) / 2; +double get avatarIconRadius => + (yaruStyled + ? kYaruTitleBarItemHeight + : isMobile + ? 42 + : 38) / + 2; double get bigPlayButtonRadius => yaruStyled ? 22 : 23; +EdgeInsets get filterPanelPadding => + EdgeInsets.only(top: isMobile ? 10 : 0, bottom: 10); + EdgeInsets get bigPlayButtonPadding => EdgeInsets.symmetric(horizontal: yaruStyled ? 2.5 : 5); diff --git a/lib/local_audio/view/artist_page.dart b/lib/local_audio/view/artist_page.dart index e4befec89..eaf827fbc 100644 --- a/lib/local_audio/view/artist_page.dart +++ b/lib/local_audio/view/artist_page.dart @@ -72,7 +72,6 @@ class ArtistPage extends StatelessWidget with WatchItMixin { resizeToAvoidBottomInset: isMobile ? false : null, appBar: HeaderBar( adaptive: true, - title: isMobile ? null : Text(pageId), actions: [ Padding( padding: appBarSingleActionSpacing, diff --git a/lib/local_audio/view/local_audio_control_panel.dart b/lib/local_audio/view/local_audio_control_panel.dart index 270f807ce..724f4688b 100644 --- a/lib/local_audio/view/local_audio_control_panel.dart +++ b/lib/local_audio/view/local_audio_control_panel.dart @@ -26,7 +26,12 @@ class LocalAudioControlPanel extends StatelessWidget with WatchItMixin { selectedFirst: false, clearOnSelect: false, labels: LocalAudioView.values - .map((e) => Text(e.localize(context.l10n))) + .map( + (e) => Text( + e.localize(context.l10n), + style: chipTextStyle(theme), + ), + ) .toList(), isSelected: LocalAudioView.values .map((e) => e == LocalAudioView.values[index]) diff --git a/lib/local_audio/view/local_audio_page.dart b/lib/local_audio/view/local_audio_page.dart index 556d7f4cd..e8504e1aa 100644 --- a/lib/local_audio/view/local_audio_page.dart +++ b/lib/local_audio/view/local_audio_page.dart @@ -9,6 +9,7 @@ import '../../common/view/common_widgets.dart'; import '../../common/view/header_bar.dart'; import '../../common/view/icons.dart'; import '../../common/view/search_button.dart'; +import '../../common/view/sliver_filter_app_bar.dart'; import '../../common/view/theme.dart'; import '../../constants.dart'; import '../../extensions/build_context_x.dart'; @@ -82,20 +83,15 @@ class _LocalAudioPageState extends State { builder: (context, constraints) { return CustomScrollView( slivers: [ - SliverPadding( + SliverFilterAppBar( padding: getAdaptiveHorizontalPadding(constraints: constraints) - .copyWith(bottom: 5), - sliver: SliverAppBar( - shape: const RoundedRectangleBorder(side: BorderSide.none), - elevation: 0, - backgroundColor: context.t.scaffoldBackgroundColor, - automaticallyImplyLeading: false, - pinned: true, - centerTitle: true, - titleSpacing: 0, - stretch: true, - title: const LocalAudioControlPanel(), + .copyWith( + bottom: filterPanelPadding.bottom, + left: filterPanelPadding.left, + top: filterPanelPadding.top, + right: filterPanelPadding.right, ), + title: const LocalAudioControlPanel(), ), SliverPadding( padding: getAdaptiveHorizontalPadding(constraints: constraints), diff --git a/lib/player/player_service.dart b/lib/player/player_service.dart index 4ee46d536..22a2e3112 100644 --- a/lib/player/player_service.dart +++ b/lib/player/player_service.dart @@ -482,6 +482,7 @@ class PlayerService { if (playerState?.audio != null) { _setAudio(playerState!.audio!); + _setRemoteImageUrl(playerState.audio!.imageUrl); if (playerState.duration != null) { setDuration(playerState.duration!.parsedDuration); @@ -789,7 +790,7 @@ class PlayerService { Future _writePlayerState() async { final playerState = PlayerState( - audio: _audio, + audio: _audio?.copyWith(imageUrl: _remoteImageUrl), duration: _duration?.toString(), position: _position?.toString(), queue: diff --git a/lib/playlists/view/playlist_page.dart b/lib/playlists/view/playlist_page.dart index 1b3e628af..3f786f71c 100644 --- a/lib/playlists/view/playlist_page.dart +++ b/lib/playlists/view/playlist_page.dart @@ -87,7 +87,6 @@ class PlaylistPage extends StatelessWidget { resizeToAvoidBottomInset: isMobile ? false : null, appBar: HeaderBar( adaptive: true, - title: Text(playlist.key), actions: [ Padding( padding: appBarSingleActionSpacing, diff --git a/lib/podcasts/view/podcast_collection_control_panel.dart b/lib/podcasts/view/podcast_collection_control_panel.dart index e4b1aa4e9..a61c3a76d 100644 --- a/lib/podcasts/view/podcast_collection_control_panel.dart +++ b/lib/podcasts/view/podcast_collection_control_panel.dart @@ -35,9 +35,13 @@ class PodcastCollectionControlPanel extends StatelessWidget with WatchItMixin { clearOnSelect: false, selectedFirst: false, labels: [ - Text(context.l10n.newEpisodes), + Text( + context.l10n.newEpisodes, + style: chipTextStyle(theme), + ), Text( context.l10n.downloadsOnly, + style: chipTextStyle(theme), ), ], isSelected: [ diff --git a/lib/podcasts/view/podcast_page.dart b/lib/podcasts/view/podcast_page.dart index 9efc203e2..4738b6b2d 100644 --- a/lib/podcasts/view/podcast_page.dart +++ b/lib/podcasts/view/podcast_page.dart @@ -73,7 +73,6 @@ class PodcastPage extends StatelessWidget with WatchItMixin { resizeToAvoidBottomInset: isMobile ? false : null, appBar: HeaderBar( adaptive: true, - title: isMobile ? null : Text(title), actions: [ Padding( padding: appBarSingleActionSpacing, diff --git a/lib/podcasts/view/podcasts_collection_body.dart b/lib/podcasts/view/podcasts_collection_body.dart index 43ad1b52f..d1bc86e38 100644 --- a/lib/podcasts/view/podcasts_collection_body.dart +++ b/lib/podcasts/view/podcasts_collection_body.dart @@ -79,16 +79,16 @@ class PodcastsCollectionBody extends StatelessWidget with WatchItMixin { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Align( - alignment: Alignment.center, - child: Padding( - padding: EdgeInsets.only(top: 10), - child: PodcastCollectionControlPanel(), + // TODO: port to sliver to get rid of this padding drama + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Container( + alignment: Alignment.center, + height: context.t.appBarTheme.toolbarHeight, + margin: filterPanelPadding, + child: const PodcastCollectionControlPanel(), ), ), - const SizedBox( - height: 15, - ), if (loading) Expanded(child: LoadingGrid(limit: subsLength)) else diff --git a/lib/radio/view/radio_lib_page.dart b/lib/radio/view/radio_lib_page.dart index b5ece150d..9bfb0ee36 100644 --- a/lib/radio/view/radio_lib_page.dart +++ b/lib/radio/view/radio_lib_page.dart @@ -35,10 +35,13 @@ class RadioLibPage extends StatelessWidget with WatchItMixin { return Column( children: [ - Align( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.only(top: 10), + // TODO: port to sliver to get rid of this padding drama + Padding( + padding: const EdgeInsets.symmetric(vertical: 5), + child: Container( + alignment: Alignment.center, + margin: filterPanelPadding, + height: context.t.appBarTheme.toolbarHeight, child: YaruChoiceChipBar( chipBackgroundColor: chipColor(theme), selectedChipBackgroundColor: chipSelectionColor(theme, false), @@ -49,9 +52,18 @@ class RadioLibPage extends StatelessWidget with WatchItMixin { .setRadioCollectionView(RadioCollectionView.values[index]), yaruChoiceChipBarStyle: YaruChoiceChipBarStyle.wrap, labels: [ - Text(context.l10n.station), - Text(context.l10n.tags), - Text(context.l10n.hearingHistory), + Text( + context.l10n.station, + style: chipTextStyle(theme), + ), + Text( + context.l10n.tags, + style: chipTextStyle(theme), + ), + Text( + context.l10n.hearingHistory, + style: chipTextStyle(theme), + ), ], isSelected: RadioCollectionView.values .map((e) => e == radioCollectionView) @@ -59,9 +71,6 @@ class RadioLibPage extends StatelessWidget with WatchItMixin { ), ), ), - const SizedBox( - height: 15, - ), Expanded( child: switch (radioCollectionView) { RadioCollectionView.stations => const StationGrid(), diff --git a/lib/radio/view/station_page.dart b/lib/radio/view/station_page.dart index 9233d04cd..c0a41a766 100644 --- a/lib/radio/view/station_page.dart +++ b/lib/radio/view/station_page.dart @@ -41,7 +41,6 @@ class StationPage extends StatelessWidget with WatchItMixin { resizeToAvoidBottomInset: isMobile ? false : null, appBar: HeaderBar( adaptive: true, - title: isMobile ? null : Text(station.title ?? station.url ?? ''), actions: [ Padding( padding: appBarSingleActionSpacing, diff --git a/lib/search/view/search_page.dart b/lib/search/view/search_page.dart index c5d41c9db..aaa0800c3 100644 --- a/lib/search/view/search_page.dart +++ b/lib/search/view/search_page.dart @@ -7,6 +7,7 @@ import '../../common/view/adaptive_container.dart'; import '../../common/view/header_bar.dart'; import '../../common/view/progress.dart'; import '../../common/view/search_button.dart'; +import '../../common/view/sliver_filter_app_bar.dart'; import '../../common/view/theme.dart'; import '../../library/library_model.dart'; import '../search_model.dart'; @@ -55,13 +56,23 @@ class SearchPage extends StatelessWidget with WatchItMixin { builder: (context, constraints) { return CustomScrollView( slivers: [ - SliverPadding( + SliverFilterAppBar( padding: getAdaptiveHorizontalPadding(constraints: constraints) .copyWith( - bottom: 5, - top: isMobile ? 10 : 0, + bottom: filterPanelPadding.bottom, + left: filterPanelPadding.left, + top: filterPanelPadding.top, + right: filterPanelPadding.right, ), - sliver: switch (audioType) { + onStretchTrigger: () async { + WidgetsBinding.instance + .addPostFrameCallback((timeStamp) async { + if (context.mounted) { + return di().search(); + } + }); + }, + title: switch (audioType) { AudioType.podcast => const SliverPodcastFilterBar(), _ => const SliverSearchTypeFilterBar(), }, diff --git a/lib/search/view/sliver_podcast_filter_bar.dart b/lib/search/view/sliver_podcast_filter_bar.dart index 2c75fab5e..4f117f1fd 100644 --- a/lib/search/view/sliver_podcast_filter_bar.dart +++ b/lib/search/view/sliver_podcast_filter_bar.dart @@ -63,136 +63,114 @@ class SliverPodcastFilterBar extends StatelessWidget with WatchItMixin { fontWeight: FontWeight.w500, ); - return SliverAppBar( - shape: const RoundedRectangleBorder(side: BorderSide.none), - elevation: 0, - backgroundColor: context.t.scaffoldBackgroundColor, - automaticallyImplyLeading: false, - pinned: true, - centerTitle: true, - titleSpacing: 0, - stretch: true, - onStretchTrigger: () async { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - if (context.mounted) { - return searchModel.search(); - } - }); - }, - title: Align( - alignment: Alignment.center, - child: SizedBox( - width: kSearchBarWidth, - child: Row( - children: [ - if (usePodcastIndex) - Expanded( - child: LanguageAutoComplete( - contentPadding: countryPillPadding, - fillColor: language != null - ? fillColor - : yaruStyled - ? theme.dividerColor - : null, - filled: language != null, - border: outlineInputBorder, - style: style, - isDense: true, - width: width, - height: height, - value: language, - favs: favLanguageCodes, - addFav: (language) { - if (language?.isoCode == null) return; - libraryModel.addFavLanguageCode(language!.isoCode); - }, - removeFav: (language) { - if (language?.isoCode == null) return; - libraryModel.removeFavLanguageCode(language!.isoCode); - }, - onSelected: (language) { - searchModel.setLanguage(language); - if (language?.isoCode != null) { - libraryModel.setLastLanguage(language!.isoCode); - } - searchModel.search(); - }, + return SizedBox( + width: kSearchBarWidth, + child: Row( + children: [ + if (usePodcastIndex) + Expanded( + child: LanguageAutoComplete( + contentPadding: countryPillPadding, + fillColor: language != null + ? fillColor + : yaruStyled + ? theme.dividerColor + : null, + filled: language != null, + border: outlineInputBorder, + style: style, + isDense: true, + width: width, + height: height, + value: language, + favs: favLanguageCodes, + addFav: (language) { + if (language?.isoCode == null) return; + libraryModel.addFavLanguageCode(language!.isoCode); + }, + removeFav: (language) { + if (language?.isoCode == null) return; + libraryModel.removeFavLanguageCode(language!.isoCode); + }, + onSelected: (language) { + searchModel.setLanguage(language); + if (language?.isoCode != null) { + libraryModel.setLastLanguage(language!.isoCode); + } + searchModel.search(); + }, + ), + ) + else + Expanded( + child: CountryAutoComplete( + contentPadding: countryPillPadding, + fillColor: fillColor, + filled: true, + border: outlineInputBorder, + style: style, + isDense: true, + width: width, + height: height, + countries: [ + ...[ + ...Country.values, + ].where( + (e) => + libraryModel.favCountryCodes.contains(e.code) == true, ), - ) - else - Expanded( - child: CountryAutoComplete( - contentPadding: countryPillPadding, - fillColor: fillColor, - filled: true, - border: outlineInputBorder, - style: style, - isDense: true, - width: width, - height: height, - countries: [ - ...[ - ...Country.values, - ].where( - (e) => - libraryModel.favCountryCodes.contains(e.code) == - true, - ), - ...[...Country.values].where( - (e) => - libraryModel.favCountryCodes.contains(e.code) == - false, - ), - ]..remove(Country.none), - onSelected: (country) { - setCountry(country); - searchModel.search(); - }, - value: country, - addFav: (v) { - if (country?.code == null) return; - libraryModel.addFavCountryCode(v!.code); - }, - removeFav: (v) { - if (country?.code == null) return; - libraryModel.removeFavCountryCode(v!.code); - }, - favs: libraryModel.favCountryCodes, + ...[...Country.values].where( + (e) => + libraryModel.favCountryCodes.contains(e.code) == false, ), - ), - const SizedBox( - width: 10, + ]..remove(Country.none), + onSelected: (country) { + setCountry(country); + searchModel.search(); + }, + value: country, + addFav: (v) { + if (country?.code == null) return; + libraryModel.addFavCountryCode(v!.code); + }, + removeFav: (v) { + if (country?.code == null) return; + libraryModel.removeFavCountryCode(v!.code); + }, + favs: libraryModel.favCountryCodes, ), - Expanded( - child: PodcastGenreAutoComplete( - contentPadding: countryPillPadding, - fillColor: podcastGenre != PodcastGenre.all - ? fillColor - : yaruStyled - ? theme.dividerColor - : null, - filled: podcastGenre != PodcastGenre.all, - border: outlineInputBorder, - style: style, - isDense: true, - width: width, - height: height, - genres: genres, - onSelected: (podcastGenre) { - if (podcastGenre != null) { - setPodcastGenre(podcastGenre); - } + ), + const SizedBox( + width: 10, + ), + Expanded( + child: PodcastGenreAutoComplete( + contentPadding: countryPillPadding, + fillColor: podcastGenre != PodcastGenre.all + ? fillColor + : yaruStyled + ? theme.dividerColor + : null, + filled: podcastGenre != PodcastGenre.all, + border: outlineInputBorder, + style: style, + isDense: true, + width: width, + height: height, + genres: genres, + onSelected: (podcastGenre) { + if (podcastGenre != null) { + setPodcastGenre(podcastGenre); + } - searchModel.search(); - }, - value: podcastGenre, - addFav: (v) {}, - removeFav: (v) {}, - ), - ), - ], + searchModel.search(); + }, + value: podcastGenre, + addFav: (v) {}, + removeFav: (v) {}, + ), ), - ), + ], ), ); } diff --git a/lib/search/view/sliver_search_type_filter_bar.dart b/lib/search/view/sliver_search_type_filter_bar.dart index 49f2cbb57..daaee23cc 100644 --- a/lib/search/view/sliver_search_type_filter_bar.dart +++ b/lib/search/view/sliver_search_type_filter_bar.dart @@ -26,67 +26,51 @@ class SliverSearchTypeFilterBar extends StatelessWidget with WatchItMixin { watchPropertyValue((SearchModel m) => m.localSearchResult); final searchQuery = watchPropertyValue((SearchModel m) => m.searchQuery); - return SliverAppBar( - shape: const RoundedRectangleBorder(side: BorderSide.none), - elevation: 0, - backgroundColor: context.t.scaffoldBackgroundColor, - automaticallyImplyLeading: false, - pinned: true, - centerTitle: true, - titleSpacing: 0, - title: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (connectedHost == null) - const Padding( - padding: EdgeInsets.only(right: 10), - child: RadioReconnectButton(), - ) - else - YaruChoiceChipBar( - yaruChoiceChipBarStyle: YaruChoiceChipBarStyle.wrap, - chipBackgroundColor: chipColor(theme), - selectedChipBackgroundColor: chipSelectionColor(theme, false), - borderColor: chipBorder(theme, false), - chipHeight: chipHeight, - clearOnSelect: false, - selectedFirst: false, - onSelected: (i) { - searchModel.setSearchType(searchTypes.elementAt(i)); - searchModel.search(manualFilter: true); - }, - labels: searchTypes - .map( - (e) => Text( - getChipText( - searchType: e, - context: context, - localSearchResult: localSearchResult, - searchQuery: searchQuery, - ), + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (connectedHost == null) + const Padding( + padding: EdgeInsets.only(right: 10), + child: RadioReconnectButton(), + ) + else + YaruChoiceChipBar( + yaruChoiceChipBarStyle: YaruChoiceChipBarStyle.wrap, + chipBackgroundColor: chipColor(theme), + selectedChipBackgroundColor: chipSelectionColor(theme, false), + borderColor: chipBorder(theme, false), + chipHeight: chipHeight, + clearOnSelect: false, + selectedFirst: false, + onSelected: (i) { + searchModel.setSearchType(searchTypes.elementAt(i)); + searchModel.search(manualFilter: true); + }, + labels: searchTypes + .map( + (e) => Text( + getChipText( + searchType: e, + context: context, + localSearchResult: localSearchResult, + searchQuery: searchQuery, ), + style: chipTextStyle(theme), + ), + ) + .toList(), + isSelected: searchTypes.none((e) => e == searchType) + ? List.generate( + searchTypes.length, + (index) => index == 0 ? true : false, ) - .toList(), - isSelected: searchTypes.none((e) => e == searchType) - ? List.generate( - searchTypes.length, - (index) => index == 0 ? true : false, - ) - : searchTypes.map((e) => e == searchType).toList(), - ), - ], - ), + : searchTypes.map((e) => e == searchType).toList(), + ), + ], ), - stretch: true, - onStretchTrigger: () async { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - if (context.mounted) { - return searchModel.search(); - } - }); - }, ); } diff --git a/pubspec.lock b/pubspec.lock index 518cee7ee..0480e6462 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1631,10 +1631,10 @@ packages: dependency: "direct overridden" description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77 url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.0" vector_math: dependency: transitive description: