mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat(android): better quick scroll/drag to scroll implementation
This commit is contained in:
parent
0a6b54da36
commit
2e2c44f0af
@ -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) {
|
||||
|
@ -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 {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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]),
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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 {
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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];
|
||||
|
@ -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(),
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user