feat(android): better quick scroll/drag to scroll implementation

This commit is contained in:
Kingkor Roy Tirtho 2023-11-13 23:17:16 +06:00
parent 0a6b54da36
commit 2e2c44f0af
13 changed files with 330 additions and 340 deletions

View File

@ -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) {

View File

@ -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,6 +82,8 @@ class UserPlaylists extends HookConsumerWidget {
return RefreshIndicator(
onRefresh: playlistsQuery.refresh,
child: SafeArea(
child: InterScrollbar(
controller: controller,
child: CustomScrollView(
controller: controller,
slivers: [
@ -145,6 +148,7 @@ class UserPlaylists extends HookConsumerWidget {
],
),
),
),
);
}
}

View File

@ -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,8 +109,7 @@ class PlayerQueue extends HookConsumerWidget {
searchText.value = '';
}
},
child: LayoutBuilder(builder: (context, constraints) {
return Column(
child: Column(
children: [
if (!floating)
Container(
@ -125,7 +125,7 @@ class PlayerQueue extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (constraints.mdAndUp || !isSearching.value) ...[
if (mediaQuery.mdAndUp || !isSearching.value) ...[
const SizedBox(width: 10),
Text(
context.l10n.tracks_in_queue(tracks.length),
@ -137,7 +137,7 @@ class PlayerQueue extends HookConsumerWidget {
),
const Spacer(),
],
if (constraints.mdAndUp || isSearching.value)
if (mediaQuery.mdAndUp || isSearching.value)
TextField(
onChanged: (value) {
searchText.value = value;
@ -145,7 +145,7 @@ class PlayerQueue extends HookConsumerWidget {
decoration: InputDecoration(
hintText: context.l10n.search,
isDense: true,
prefixIcon: constraints.smAndDown
prefixIcon: mediaQuery.smAndDown
? IconButton(
icon: const Icon(
Icons.arrow_back_ios_new_outlined,
@ -162,8 +162,8 @@ class PlayerQueue extends HookConsumerWidget {
: const Icon(SpotubeIcons.filter),
constraints: BoxConstraints(
maxHeight: 40,
maxWidth: constraints.smAndDown
? constraints.maxWidth - 20
maxWidth: mediaQuery.smAndDown
? mediaQuery.size.width - 40
: 300,
),
),
@ -175,14 +175,13 @@ class PlayerQueue extends HookConsumerWidget {
isSearching.value = !isSearching.value;
},
),
if (constraints.mdAndUp || !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,
foregroundColor: theme.textTheme.headlineSmall?.color,
),
child: Row(
children: [
@ -203,8 +202,6 @@ class PlayerQueue extends HookConsumerWidget {
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);
@ -234,8 +231,7 @@ class PlayerQueue extends HookConsumerWidget {
leadingActions: [
ReorderableDragStartListener(
index: i,
child:
const Icon(SpotubeIcons.dragHandle),
child: const Icon(SpotubeIcons.dragHandle),
),
],
),
@ -243,12 +239,13 @@ class PlayerQueue extends HookConsumerWidget {
);
},
),
),
)
else
Flexible(
child: InterScrollbar(
controller: controller,
child: ListView.builder(
controller: controller,
itemCount: filteredTracks.length,
itemBuilder: (context, i) {
final track = filteredTracks.elementAt(i);
@ -271,8 +268,7 @@ class PlayerQueue extends HookConsumerWidget {
),
),
],
);
}),
),
),
),
),

View File

@ -56,6 +56,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
useValueListenable(searchController).text,
);
final controller = useScrollController();
final searchRequest = useMemoized(() async {
if (searchTerm.trim().isEmpty) {
return <YoutubeVideoInfo>[];
@ -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]),

View File

@ -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(
return DraggableScrollbar.semicircle(
controller: controller,
thumbVisibility: thumbVisibility,
trackVisibility: trackVisibility,
thickness: thickness,
radius: radius,
notificationPredicate: notificationPredicate,
interactive: interactive ?? true,
scrollbarOrientation: scrollbarOrientation,
child: child,
),
);
}
}

View File

@ -79,8 +79,6 @@ class GenrePage extends HookConsumerWidget {
const ShimmerCategories()
else
Expanded(
child: InterScrollbar(
controller: scrollController,
child: ListView.builder(
controller: scrollController,
itemCount: categories.length,
@ -102,7 +100,6 @@ class GenrePage extends HookConsumerWidget {
},
),
),
),
],
),
),

View File

@ -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,9 +46,7 @@ class PersonalizedPage extends HookConsumerWidget {
[newReleases.pages],
);
return InterScrollbar(
controller: controller,
child: ListView(
return ListView(
controller: controller,
children: [
if (!featuredPlaylistsQuery.hasPageData &&
@ -88,7 +85,6 @@ class PersonalizedPage extends HookConsumerWidget {
);
})
],
),
);
}
}

View File

@ -71,8 +71,13 @@ class SearchPage extends HookConsumerWidget {
searchTerm.isNotEmpty;
final resultWidget = HookBuilder(
builder: (context) => InterScrollbar(
builder: (context) {
final controller = useScrollController();
return InterScrollbar(
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SafeArea(
@ -90,7 +95,8 @@ class SearchPage extends HookConsumerWidget {
),
),
),
),
);
},
);
return SafeArea(

View File

@ -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) {

View File

@ -52,6 +52,7 @@ class LogsPage extends HookWidget {
@override
Widget build(BuildContext context) {
final controller = useScrollController();
final logs = useState<List<({DateTime? date, String body})>>([]);
final rawLogs = useRef<String>("");
final path = useRef<File?>(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];

View File

@ -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(),

View File

@ -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:

View File

@ -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