From 2e2c44f0afef69bf9bc485db97d45127a0847c8e Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 13 Nov 2023 23:17:16 +0600 Subject: [PATCH] feat(android): better quick scroll/drag to scroll implementation --- lib/components/library/user_local_tracks.dart | 4 + lib/components/library/user_playlists.dart | 116 +++---- lib/components/player/player_queue.dart | 304 +++++++++--------- .../player/sibling_tracks_sheet.dart | 6 + .../inter_scrollbar/inter_scrollbar.dart | 54 +--- lib/pages/home/genres.dart | 39 ++- lib/pages/home/personalized.dart | 80 +++-- lib/pages/search/search.dart | 40 ++- lib/pages/settings/blacklist.dart | 3 + lib/pages/settings/logs.dart | 3 + lib/pages/settings/settings.dart | 4 + pubspec.lock | 9 + pubspec.yaml | 4 + 13 files changed, 328 insertions(+), 338 deletions(-) diff --git a/lib/components/library/user_local_tracks.dart b/lib/components/library/user_local_tracks.dart index c7cd06825..0546c2a7b 100644 --- a/lib/components/library/user_local_tracks.dart +++ b/lib/components/library/user_local_tracks.dart @@ -163,6 +163,8 @@ class UserLocalTracks extends HookConsumerWidget { final searchFocus = useFocusNode(); final isFiltering = useState(false); + final controller = useScrollController(); + return Column( children: [ Padding( @@ -256,7 +258,9 @@ class UserLocalTracks extends HookConsumerWidget { ref.refresh(localTracksProvider); }, child: InterScrollbar( + controller: controller, child: ListView.builder( + controller: controller, physics: const AlwaysScrollableScrollPhysics(), itemCount: filteredTracks.length, itemBuilder: (context, index) { diff --git a/lib/components/library/user_playlists.dart b/lib/components/library/user_playlists.dart index ecf4fa124..0102a3c7a 100644 --- a/lib/components/library/user_playlists.dart +++ b/lib/components/library/user_playlists.dart @@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playlist/playlist_create_dialog.dart'; +import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/playlist/playlist_card.dart'; @@ -81,68 +82,71 @@ class UserPlaylists extends HookConsumerWidget { return RefreshIndicator( onRefresh: playlistsQuery.refresh, child: SafeArea( - child: CustomScrollView( + child: InterScrollbar( controller: controller, - slivers: [ - SliverToBoxAdapter( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: SearchBar( - onChanged: (value) => searchText.value = value, - hintText: context.l10n.filter_playlists, - leading: const Icon(SpotubeIcons.filter), - ), - ), - Row( - children: [ - const SizedBox(width: 10), - const PlaylistCreateDialogButton(), - const SizedBox(width: 10), - ElevatedButton.icon( - icon: const Icon(SpotubeIcons.magic), - label: Text(context.l10n.generate_playlist), - onPressed: () { - GoRouter.of(context).push("/library/generate"); - }, + child: CustomScrollView( + controller: controller, + slivers: [ + SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: SearchBar( + onChanged: (value) => searchText.value = value, + hintText: context.l10n.filter_playlists, + leading: const Icon(SpotubeIcons.filter), ), - const SizedBox(width: 10), - ], - ), - ], + ), + Row( + children: [ + const SizedBox(width: 10), + const PlaylistCreateDialogButton(), + const SizedBox(width: 10), + ElevatedButton.icon( + icon: const Icon(SpotubeIcons.magic), + label: Text(context.l10n.generate_playlist), + onPressed: () { + GoRouter.of(context).push("/library/generate"); + }, + ), + const SizedBox(width: 10), + ], + ), + ], + ), ), - ), - const SliverToBoxAdapter( - child: SizedBox(height: 10), - ), - SliverGrid.builder( - itemCount: playlists.length + 1, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: DesktopTools.platform.isMobile ? 225 : 250, - crossAxisSpacing: 8, - mainAxisSpacing: 8, + const SliverToBoxAdapter( + child: SizedBox(height: 10), ), - itemBuilder: (context, index) { - if (index == playlists.length) { - if (!playlistsQuery.hasNextPage) { - return const SizedBox.shrink(); - } + SliverGrid.builder( + itemCount: playlists.length + 1, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisExtent: DesktopTools.platform.isMobile ? 225 : 250, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemBuilder: (context, index) { + if (index == playlists.length) { + if (!playlistsQuery.hasNextPage) { + return const SizedBox.shrink(); + } - return Waypoint( - controller: controller, - isGrid: true, - onTouchEdge: playlistsQuery.fetchNext, - child: const ShimmerPlaybuttonCard(count: 1), - ); - } + return Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: playlistsQuery.fetchNext, + child: const ShimmerPlaybuttonCard(count: 1), + ); + } - return PlaylistCard(playlists[index]); - }, - ) - ], + return PlaylistCard(playlists[index]); + }, + ) + ], + ), ), ), ); diff --git a/lib/components/player/player_queue.dart b/lib/components/player/player_queue.dart index 2d8ba3298..9e303cb87 100644 --- a/lib/components/player/player_queue.dart +++ b/lib/components/player/player_queue.dart @@ -44,6 +44,7 @@ class PlayerQueue extends HookConsumerWidget { topRight: Radius.circular(10), ); final theme = Theme.of(context); + final mediaQuery = MediaQuery.of(context); final headlineColor = theme.textTheme.headlineSmall?.color; final filteredTracks = useMemoized( @@ -108,171 +109,166 @@ class PlayerQueue extends HookConsumerWidget { searchText.value = ''; } }, - child: LayoutBuilder(builder: (context, constraints) { - return Column( - children: [ - if (!floating) - Container( - height: 5, - width: 100, - margin: const EdgeInsets.only(bottom: 5, top: 2), - decoration: BoxDecoration( - color: headlineColor, - borderRadius: BorderRadius.circular(20), - ), + child: Column( + children: [ + if (!floating) + Container( + height: 5, + width: 100, + margin: const EdgeInsets.only(bottom: 5, top: 2), + decoration: BoxDecoration( + color: headlineColor, + borderRadius: BorderRadius.circular(20), ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (constraints.mdAndUp || !isSearching.value) ...[ - const SizedBox(width: 10), - Text( - context.l10n.tracks_in_queue(tracks.length), - style: TextStyle( - color: headlineColor, - fontWeight: FontWeight.bold, - fontSize: 18, - ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (mediaQuery.mdAndUp || !isSearching.value) ...[ + const SizedBox(width: 10), + Text( + context.l10n.tracks_in_queue(tracks.length), + style: TextStyle( + color: headlineColor, + fontWeight: FontWeight.bold, + fontSize: 18, ), - const Spacer(), - ], - if (constraints.mdAndUp || isSearching.value) - TextField( - onChanged: (value) { - searchText.value = value; - }, - decoration: InputDecoration( - hintText: context.l10n.search, - isDense: true, - prefixIcon: constraints.smAndDown - ? IconButton( - icon: const Icon( - Icons.arrow_back_ios_new_outlined, - ), - onPressed: () { - isSearching.value = false; - searchText.value = ''; - }, - style: IconButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: const Size.square(20), - ), - ) - : const Icon(SpotubeIcons.filter), - constraints: BoxConstraints( - maxHeight: 40, - maxWidth: constraints.smAndDown - ? constraints.maxWidth - 20 - : 300, - ), + ), + const Spacer(), + ], + if (mediaQuery.mdAndUp || isSearching.value) + TextField( + onChanged: (value) { + searchText.value = value; + }, + decoration: InputDecoration( + hintText: context.l10n.search, + isDense: true, + prefixIcon: mediaQuery.smAndDown + ? IconButton( + icon: const Icon( + Icons.arrow_back_ios_new_outlined, + ), + onPressed: () { + isSearching.value = false; + searchText.value = ''; + }, + style: IconButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: const Size.square(20), + ), + ) + : const Icon(SpotubeIcons.filter), + constraints: BoxConstraints( + maxHeight: 40, + maxWidth: mediaQuery.smAndDown + ? mediaQuery.size.width - 40 + : 300, ), - ) - else - IconButton.filledTonal( - icon: const Icon(SpotubeIcons.filter), - onPressed: () { - isSearching.value = !isSearching.value; - }, ), - if (constraints.mdAndUp || !isSearching.value) ...[ - const SizedBox(width: 10), - FilledButton( - style: FilledButton.styleFrom( - backgroundColor: - theme.scaffoldBackgroundColor.withOpacity(0.5), - foregroundColor: - theme.textTheme.headlineSmall?.color, - ), - child: Row( - children: [ - const Icon(SpotubeIcons.playlistRemove), - const SizedBox(width: 5), - Text(context.l10n.clear_all), - ], - ), - onPressed: () { - playlistNotifier.stop(); - Navigator.of(context).pop(); - }, + ) + else + IconButton.filledTonal( + icon: const Icon(SpotubeIcons.filter), + onPressed: () { + isSearching.value = !isSearching.value; + }, + ), + if (mediaQuery.mdAndUp || !isSearching.value) ...[ + const SizedBox(width: 10), + FilledButton( + style: FilledButton.styleFrom( + backgroundColor: + theme.scaffoldBackgroundColor.withOpacity(0.5), + foregroundColor: theme.textTheme.headlineSmall?.color, ), - const SizedBox(width: 10), - ], + child: Row( + children: [ + const Icon(SpotubeIcons.playlistRemove), + const SizedBox(width: 5), + Text(context.l10n.clear_all), + ], + ), + onPressed: () { + playlistNotifier.stop(); + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 10), ], - ), - const SizedBox(height: 10), - if (!isSearching.value && searchText.value.isEmpty) - Flexible( - child: InterScrollbar( - controller: controller, - child: ReorderableListView.builder( - onReorder: (oldIndex, newIndex) { - playlistNotifier.moveTrack(oldIndex, newIndex); - }, - scrollController: controller, - itemCount: tracks.length, - shrinkWrap: true, - buildDefaultDragHandles: false, - itemBuilder: (context, i) { - final track = tracks.elementAt(i); - return AutoScrollTag( - key: ValueKey(i), - controller: controller, + ], + ), + const SizedBox(height: 10), + if (!isSearching.value && searchText.value.isEmpty) + Flexible( + child: ReorderableListView.builder( + onReorder: (oldIndex, newIndex) { + playlistNotifier.moveTrack(oldIndex, newIndex); + }, + scrollController: controller, + itemCount: tracks.length, + shrinkWrap: true, + buildDefaultDragHandles: false, + itemBuilder: (context, i) { + final track = tracks.elementAt(i); + return AutoScrollTag( + key: ValueKey(i), + controller: controller, + index: i, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: TrackTile( index: i, - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0), - child: TrackTile( + track: track, + onTap: () async { + if (playlist.activeTrack?.id == track.id) { + return; + } + await playlistNotifier.jumpToTrack(track); + }, + leadingActions: [ + ReorderableDragStartListener( index: i, - track: track, - onTap: () async { - if (playlist.activeTrack?.id == track.id) { - return; - } - await playlistNotifier.jumpToTrack(track); - }, - leadingActions: [ - ReorderableDragStartListener( - index: i, - child: - const Icon(SpotubeIcons.dragHandle), - ), - ], + child: const Icon(SpotubeIcons.dragHandle), ), - ), - ); - }, - ), - ), - ) - else - Flexible( - child: InterScrollbar( - child: ListView.builder( - itemCount: filteredTracks.length, - itemBuilder: (context, i) { - final track = filteredTracks.elementAt(i); - return Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0), - child: TrackTile( - index: i, - track: track, - onTap: () async { - if (playlist.activeTrack?.id == track.id) { - return; - } - await playlistNotifier.jumpToTrack(track); - }, - ), - ); - }, - ), + ], + ), + ), + ); + }, + ), + ) + else + Flexible( + child: InterScrollbar( + controller: controller, + child: ListView.builder( + controller: controller, + itemCount: filteredTracks.length, + itemBuilder: (context, i) { + final track = filteredTracks.elementAt(i); + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: TrackTile( + index: i, + track: track, + onTap: () async { + if (playlist.activeTrack?.id == track.id) { + return; + } + await playlistNotifier.jumpToTrack(track); + }, + ), + ); + }, ), ), - ], - ); - }), + ), + ], + ), ), ), ), diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart index 14c042b84..8dc41026c 100644 --- a/lib/components/player/sibling_tracks_sheet.dart +++ b/lib/components/player/sibling_tracks_sheet.dart @@ -56,6 +56,8 @@ class SiblingTracksSheet extends HookConsumerWidget { useValueListenable(searchController).text, ); + final controller = useScrollController(); + final searchRequest = useMemoized(() async { if (searchTerm.trim().isEmpty) { return []; @@ -204,8 +206,10 @@ class SiblingTracksSheet extends HookConsumerWidget { transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child), child: InterScrollbar( + controller: controller, child: switch (isSearching.value) { false => ListView.builder( + controller: controller, itemCount: siblings.length, itemBuilder: (context, index) => itemBuilder(siblings[index]), @@ -223,7 +227,9 @@ class SiblingTracksSheet extends HookConsumerWidget { } return InterScrollbar( + controller: controller, child: ListView.builder( + controller: controller, itemCount: snapshot.data!.length, itemBuilder: (context, index) => itemBuilder(snapshot.data![index]), diff --git a/lib/components/shared/inter_scrollbar/inter_scrollbar.dart b/lib/components/shared/inter_scrollbar/inter_scrollbar.dart index 05eb174a7..11f758290 100644 --- a/lib/components/shared/inter_scrollbar/inter_scrollbar.dart +++ b/lib/components/shared/inter_scrollbar/inter_scrollbar.dart @@ -1,29 +1,16 @@ +import 'package:draggable_scrollbar/draggable_scrollbar.dart'; import 'package:flutter/material.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; class InterScrollbar extends HookWidget { final Widget child; - final ScrollController? controller; - final bool? thumbVisibility; - final bool? trackVisibility; - final double? thickness; - final Radius? radius; - final bool Function(ScrollNotification)? notificationPredicate; - final bool? interactive; - final ScrollbarOrientation? scrollbarOrientation; + final ScrollController controller; const InterScrollbar({ super.key, required this.child, - this.controller, - this.thumbVisibility, - this.trackVisibility, - this.thickness, - this.radius, - this.notificationPredicate, - this.interactive, - this.scrollbarOrientation, + required this.controller, }); @override @@ -32,38 +19,9 @@ class InterScrollbar extends HookWidget { if (DesktopTools.platform.isDesktop) return child; - return ScrollbarTheme( - data: theme.scrollbarTheme.copyWith( - crossAxisMargin: 10, - minThumbLength: 80, - thickness: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.hovered) || - states.contains(MaterialState.dragged) || - states.contains(MaterialState.pressed)) { - return 40; - } - return 20; - }), - radius: const Radius.circular(20), - thumbColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.hovered) || - states.contains(MaterialState.dragged)) { - return theme.colorScheme.onSurface.withOpacity(0.5); - } - return theme.colorScheme.onSurface.withOpacity(0.3); - }), - ), - child: Scrollbar( - controller: controller, - thumbVisibility: thumbVisibility, - trackVisibility: trackVisibility, - thickness: thickness, - radius: radius, - notificationPredicate: notificationPredicate, - interactive: interactive ?? true, - scrollbarOrientation: scrollbarOrientation, - child: child, - ), + return DraggableScrollbar.semicircle( + controller: controller, + child: child, ); } } diff --git a/lib/pages/home/genres.dart b/lib/pages/home/genres.dart index 076305f21..6861853df 100644 --- a/lib/pages/home/genres.dart +++ b/lib/pages/home/genres.dart @@ -79,28 +79,25 @@ class GenrePage extends HookConsumerWidget { const ShimmerCategories() else Expanded( - child: InterScrollbar( + child: ListView.builder( controller: scrollController, - child: ListView.builder( - controller: scrollController, - itemCount: categories.length, - itemBuilder: (context, index) { - return AnimatedSwitcher( - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - duration: const Duration(milliseconds: 300), - child: searchController.text.isEmpty && - index == categories.length - 1 && - categoriesQuery.hasNextPage - ? const ShimmerCategories() - : CategoryCard(categories[index]), - ); - }, - ), + itemCount: categories.length, + itemBuilder: (context, index) { + return AnimatedSwitcher( + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + duration: const Duration(milliseconds: 300), + child: searchController.text.isEmpty && + index == categories.length - 1 && + categoriesQuery.hasNextPage + ? const ShimmerCategories() + : CategoryCard(categories[index]), + ); + }, ), ), ], diff --git a/lib/pages/home/personalized.dart b/lib/pages/home/personalized.dart index 8a18fd0b9..b596a8202 100644 --- a/lib/pages/home/personalized.dart +++ b/lib/pages/home/personalized.dart @@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/shimmers/shimmer_categories.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication_provider.dart'; @@ -47,48 +46,45 @@ class PersonalizedPage extends HookConsumerWidget { [newReleases.pages], ); - return InterScrollbar( + return ListView( controller: controller, - child: ListView( - controller: controller, - children: [ - if (!featuredPlaylistsQuery.hasPageData && - !featuredPlaylistsQuery.isLoadingNextPage) - const ShimmerCategories() - else - HorizontalPlaybuttonCardView( - items: playlists.toList(), - title: Text(context.l10n.featured), - hasNextPage: featuredPlaylistsQuery.hasNextPage, - onFetchMore: featuredPlaylistsQuery.fetchNext, - ), - if (auth != null && - newReleases.hasPageData && - userArtistsQuery.hasData && - !newReleases.isLoadingNextPage) - HorizontalPlaybuttonCardView( - items: albums, - title: Text(context.l10n.new_releases), - hasNextPage: newReleases.hasNextPage, - onFetchMore: newReleases.fetchNext, - ), - ...?madeForUser.data?["content"]?["items"]?.map((item) { - final playlists = item["content"]?["items"] - ?.where((itemL2) => itemL2["type"] == "playlist") - .map((itemL2) => PlaylistSimple.fromJson(itemL2)) - .toList() - .cast() ?? - []; - if (playlists.isEmpty) return const SizedBox.shrink(); - return HorizontalPlaybuttonCardView( - items: playlists, - title: Text(item["name"] ?? ""), - hasNextPage: false, - onFetchMore: () {}, - ); - }) - ], - ), + children: [ + if (!featuredPlaylistsQuery.hasPageData && + !featuredPlaylistsQuery.isLoadingNextPage) + const ShimmerCategories() + else + HorizontalPlaybuttonCardView( + items: playlists.toList(), + title: Text(context.l10n.featured), + hasNextPage: featuredPlaylistsQuery.hasNextPage, + onFetchMore: featuredPlaylistsQuery.fetchNext, + ), + if (auth != null && + newReleases.hasPageData && + userArtistsQuery.hasData && + !newReleases.isLoadingNextPage) + HorizontalPlaybuttonCardView( + items: albums, + title: Text(context.l10n.new_releases), + hasNextPage: newReleases.hasNextPage, + onFetchMore: newReleases.fetchNext, + ), + ...?madeForUser.data?["content"]?["items"]?.map((item) { + final playlists = item["content"]?["items"] + ?.where((itemL2) => itemL2["type"] == "playlist") + .map((itemL2) => PlaylistSimple.fromJson(itemL2)) + .toList() + .cast() ?? + []; + if (playlists.isEmpty) return const SizedBox.shrink(); + return HorizontalPlaybuttonCardView( + items: playlists, + title: Text(item["name"] ?? ""), + hasNextPage: false, + onFetchMore: () {}, + ); + }) + ], ); } } diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index d659e8e3b..b19162faa 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -71,26 +71,32 @@ class SearchPage extends HookConsumerWidget { searchTerm.isNotEmpty; final resultWidget = HookBuilder( - builder: (context) => InterScrollbar( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SearchTracksSection(query: searchTrack), - SearchPlaylistsSection(query: searchPlaylist), - const SizedBox(height: 20), - SearchArtistsSection(query: searchArtist), - const SizedBox(height: 20), - SearchAlbumsSection(query: searchAlbum), - ], + builder: (context) { + final controller = useScrollController(); + + return InterScrollbar( + controller: controller, + child: SingleChildScrollView( + controller: controller, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SearchTracksSection(query: searchTrack), + SearchPlaylistsSection(query: searchPlaylist), + const SizedBox(height: 20), + SearchArtistsSection(query: searchArtist), + const SizedBox(height: 20), + SearchAlbumsSection(query: searchAlbum), + ], + ), ), ), ), - ), - ), + ); + }, ); return SafeArea( diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index 69800633f..b4ce50440 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -15,6 +15,7 @@ class BlackListPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final controller = useScrollController(); final blacklist = ref.watch(BlackListNotifier.provider); final searchText = useState(""); @@ -58,7 +59,9 @@ class BlackListPage extends HookConsumerWidget { ), ), InterScrollbar( + controller: controller, child: ListView.builder( + controller: controller, shrinkWrap: true, itemCount: filteredBlacklist.length, itemBuilder: (context, index) { diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 91d87fbb8..cfb28d182 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -52,6 +52,7 @@ class LogsPage extends HookWidget { @override Widget build(BuildContext context) { + final controller = useScrollController(); final logs = useState>([]); final rawLogs = useRef(""); final path = useRef(null); @@ -93,7 +94,9 @@ class LogsPage extends HookWidget { ), body: SafeArea( child: InterScrollbar( + controller: controller, child: ListView.builder( + controller: controller, itemCount: logs.value.length, itemBuilder: (context, index) { final log = logs.value[index]; diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index baf245b41..84b51d4db 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; @@ -20,6 +21,7 @@ class SettingsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final controller = useScrollController(); final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); return SafeArea( @@ -36,7 +38,9 @@ class SettingsPage extends HookConsumerWidget { child: Container( constraints: const BoxConstraints(maxWidth: 1366), child: InterScrollbar( + controller: controller, child: ListView( + controller: controller, children: [ const SettingsAccountSection(), const SettingsLanguageRegionSection(), diff --git a/pubspec.lock b/pubspec.lock index 9c0161c6b..3d072e096 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -465,6 +465,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + draggable_scrollbar: + dependency: "direct main" + description: + path: "." + ref: cfd570035bf393de541d32e9b28808b5d7e602df + resolved-ref: cfd570035bf393de541d32e9b28808b5d7e602df + url: "https://github.com/thielepaul/flutter-draggable-scrollbar.git" + source: git + version: "0.1.0" duration: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 64b2b6a36..f9c1155f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -106,6 +106,10 @@ dependencies: simple_icons: ^7.10.0 audio_service_mpris: ^0.1.0 file_picker: ^6.0.0 + draggable_scrollbar: + git: + url: https://github.com/thielepaul/flutter-draggable-scrollbar.git + ref: cfd570035bf393de541d32e9b28808b5d7e602df dev_dependencies: build_runner: ^2.3.2