fix: improve scroll handling on home page to avoid unintentional scroll event detection

This commit is contained in:
Michael Thomas 2023-02-09 20:00:05 -05:00
parent 67efdc0289
commit d0d881fbbe
6 changed files with 211 additions and 138 deletions

View File

@ -8,9 +8,11 @@ import 'package:furman_now/src/widgets/scroll_view_height.dart';
class HomeContent extends StatefulWidget { class HomeContent extends StatefulWidget {
final bool collapse; final bool collapse;
final ScrollController controller;
const HomeContent({ const HomeContent({
required this.collapse, required this.collapse,
required this.controller,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -19,13 +21,12 @@ class HomeContent extends StatefulWidget {
} }
class _HomeContentState extends State<HomeContent> { class _HomeContentState extends State<HomeContent> {
final ScrollController _controller = ScrollController();
bool _collapse = false; bool _collapse = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.collapse != _collapse) { if (widget.collapse != _collapse) {
_controller.animateTo( widget.controller.animateTo(
0, 0,
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -35,7 +36,7 @@ class _HomeContentState extends State<HomeContent> {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) => ScrollViewWithHeight( builder: (context, constraints) => ScrollViewWithHeight(
controller: _controller, controller: widget.controller,
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: TweenAnimationBuilder<double>( child: TweenAnimationBuilder<double>(
@ -50,7 +51,10 @@ class _HomeContentState extends State<HomeContent> {
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: borderRadius:
BorderRadius.vertical(top: Radius.circular(30)), BorderRadius.vertical(
top: Radius.circular(30),
bottom: Radius.circular(30),
),
), ),
padding: const EdgeInsets.only(bottom: 15), padding: const EdgeInsets.only(bottom: 15),
margin: EdgeInsets.only(top: margin), margin: EdgeInsets.only(top: margin),

View File

@ -44,6 +44,7 @@ class HomePageHeader extends StatelessWidget {
color: Color(0xff26183d), color: Color(0xff26183d),
fontSize: 36, fontSize: 36,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
height: 1.2,
)), )),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:furman_now/src/routes/index.gr.dart'; import 'package:furman_now/src/routes/index.gr.dart';
import 'package:furman_now/src/screens/home/content.dart'; import 'package:furman_now/src/screens/home/content.dart';
import 'package:furman_now/src/screens/home/state.dart'; import 'package:furman_now/src/screens/home/state.dart';
@ -13,75 +14,124 @@ class HomeScreen extends StatefulWidget {
} }
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
bool _collapse = false; final ScrollController _controller = ScrollController();
double overscrollTopAmount = 0;
double overscrollBottomAmount = 0;
@override
void initState() {
super.initState();
_controller.addListener(updateScrollPosition);
}
updateScrollPosition() {
// bottom overscroll
if (_controller.position.pixels > _controller.position.maxScrollExtent) {
setState(() {
overscrollBottomAmount =
_controller.position.pixels - _controller.position.maxScrollExtent;
});
} else {
if (overscrollBottomAmount != 0) {
setState(() {
overscrollBottomAmount = 0;
});
}
}
// top overscroll
if (_controller.position.pixels < 0) {
setState(() {
overscrollTopAmount =
_controller.position.pixels.abs();
});
} else {
if (overscrollTopAmount != 0) {
setState(() {
overscrollTopAmount = 0;
});
}
}
}
handleScrollEvent(BuildContext context) {
const scrollMessageSensitivity = 20;
const pageSwitchSensitivity = 60;
var offsetAmount = overscrollTopAmount.abs();
if (overscrollTopAmount != 0) {
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const StudentIdRoute());
context.read<HomePageState>().collapse = true;
} else if (offsetAmount > scrollMessageSensitivity) {
context.read<HomePageState>().showScrollMessage = true;
} else {
context.read<HomePageState>().showScrollMessage = false;
}
} else {
context.read<HomePageState>().showScrollMessage = false;
if (_controller.position.pixels > pageSwitchSensitivity) {
context.router.navigate(const HomeRoute());
context.read<HomePageState>().collapse = false;
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider( return ChangeNotifierProvider(
create: (context) => HomePageState(), create: (context) => HomePageState(),
builder: (context, _) => Scaffold( builder: (context, _) => Scaffold(
body: Container( body: AnnotatedRegion<SystemUiOverlayStyle>(
color: const Color(0xffb7acc9), value: SystemUiOverlayStyle.light,
child: SafeArea(
child: Container( child: Container(
color: const Color(0xffb7acc9), color: const Color(0xffb7acc9),
child: Stack( child: SafeArea(
fit: StackFit.loose, child: Container(
children: [ color: const Color(0xffb7acc9),
const AutoTabsRouter( child: Stack(
routes: [ fit: StackFit.loose,
HomeRoute(), children: [
StudentIdRoute(), // overscroll indicator color
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: overscrollBottomAmount + 30,
color: Colors.grey.shade50,
),
),
const AutoTabsRouter(
routes: [
HomeRoute(),
StudentIdRoute(),
],
),
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
handleScrollEvent(context);
}
return true;
},
child: Consumer<HomePageState>(
builder: (context, state, _) =>
ClipRect(child: HomeContent(
controller: _controller,
collapse: state.collapse,
),
),
),
),
], ],
), ),
NotificationListener<ScrollNotification>( ),
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
final offset = notification.metrics.pixels;
const scrollMessageSensitivity = 20;
const pageSwitchSensitivity = 60;
if (offset < 0) {
var offsetAmount = offset.abs();
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const StudentIdRoute());
setState(() {
_collapse = true;
});
} else if (offsetAmount > scrollMessageSensitivity) {
if (!context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = true;
}
} else {
if (context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = false;
}
}
} else {
var offsetAmount = offset.abs();
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const HomeRoute());
setState(() {
_collapse = false;
});
} else {
if (context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = false;
}
}
}
}
return true;
},
child: ClipRect(child: HomeContent(
collapse: _collapse,
)),
),
],
), ),
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -2,13 +2,21 @@ import 'package:flutter/material.dart';
class HomePageState extends ChangeNotifier { class HomePageState extends ChangeNotifier {
bool _showScrollMessage = false; bool _showScrollMessage = false;
bool _collapse = false;
bool get showScrollMessage { bool get showScrollMessage { return _showScrollMessage; }
return _showScrollMessage; set showScrollMessage(bool value) {
if (value != _showScrollMessage) {
_showScrollMessage = value;
notifyListeners();
}
} }
set showScrollMessage(bool value) { bool get collapse { return _collapse; }
_showScrollMessage = value; set collapse(bool value) {
notifyListeners(); if (value != _collapse) {
_collapse = value;
notifyListeners();
}
} }
} }

View File

@ -7,6 +7,9 @@ import 'package:flutter_remix/flutter_remix.dart';
import 'package:furman_now/src/routes/index.gr.dart'; import 'package:furman_now/src/routes/index.gr.dart';
import 'package:furman_now/src/services/get_app/barcode/barcode_service.dart'; import 'package:furman_now/src/services/get_app/barcode/barcode_service.dart';
import 'package:furman_now/src/utils/theme.dart'; import 'package:furman_now/src/utils/theme.dart';
import 'package:provider/provider.dart';
import '../home/state.dart';
class StudentIdScreen extends StatefulWidget { class StudentIdScreen extends StatefulWidget {
const StudentIdScreen({Key? key}) : super(key: key); const StudentIdScreen({Key? key}) : super(key: key);
@ -44,88 +47,94 @@ class _StudentIdScreenState extends State<StudentIdScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Listener( body: WillPopScope(
onPointerMove: (details) { onWillPop: () async {
int sensitivity = 8; context.read<HomePageState>().collapse = false;
if (details.delta.dy > sensitivity) { return true;
// Down Swipe
} else if (details.delta.dy < -sensitivity) {
context.router.navigate(HomeRoute());
}
}, },
child: Stack( child: Listener(
children: [ onPointerMove: (details) {
Container( int sensitivity = 8;
color: const Color(0xffb7acc9), if (details.delta.dy > sensitivity) {
child: SafeArea( // Down Swipe
child: ListView( } else if (details.delta.dy < -sensitivity) {
padding: const EdgeInsets.all(40), context.router.navigate(HomeRoute());
children: [ }
Text( },
"Furman ID", child: Stack(
style: furmanTextStyle(const TextStyle( children: [
color: Color(0xff26183d), Container(
fontSize: 36, color: const Color(0xffb7acc9),
fontWeight: FontWeight.w800 child: SafeArea(
)), child: ListView(
), padding: const EdgeInsets.all(40),
const SizedBox(height: 30), children: [
Center( Text(
child: Container( "Furman ID",
height: 200, style: furmanTextStyle(const TextStyle(
width: 200, color: Color(0xff26183d),
decoration: BoxDecoration( fontSize: 36,
color: Colors.grey[200], fontWeight: FontWeight.w800
borderRadius: BorderRadius.circular(40), )),
), ),
child: Icon( const SizedBox(height: 30),
FlutterRemix.user_3_line, Center(
size: 120, child: Container(
color: Colors.grey[400], height: 200,
width: 200,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(40),
),
child: Icon(
FlutterRemix.user_3_line,
size: 120,
color: Colors.grey[400],
),
), ),
), ),
), const SizedBox(height: 20),
const SizedBox(height: 20), Center(child: Text(
Center(child: Text( "Michael Thomas",
"Michael Thomas", style: furmanTextStyle(const TextStyle(
style: furmanTextStyle(const TextStyle( color: Color(0xff26183d),
color: Color(0xff26183d), fontSize: 28,
fontSize: 28, fontWeight: FontWeight.w700
fontWeight: FontWeight.w700 )),
)), )),
)), const SizedBox(height: 5),
const SizedBox(height: 5), Center(child: Text(
Center(child: Text( "5001962",
"5001962", style: furmanTextStyle(const TextStyle(
style: furmanTextStyle(const TextStyle( color: Color(0xff26183d),
color: Color(0xff26183d), fontSize: 20,
fontSize: 20, fontWeight: FontWeight.w500
fontWeight: FontWeight.w500 )),
)), )),
)), const SizedBox(height: 20),
const SizedBox(height: 20), Container(
Container( decoration: const BoxDecoration(
decoration: const BoxDecoration( color: Colors.white,
color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(10)),
borderRadius: BorderRadius.all(Radius.circular(10)), ),
// hack since the barcode has a weird intrinsic size for some reason
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return BarcodeWidget(
barcode: Barcode.pdf417(moduleHeight: 4),
data: barcodeNumber,
margin: const EdgeInsets.all(10),
height: constraints.maxWidth / 3,
);
},
),
), ),
// hack since the barcode has a weird intrinsic size for some reason ],
child: LayoutBuilder( ),
builder: (BuildContext context, BoxConstraints constraints) {
return BarcodeWidget(
barcode: Barcode.pdf417(moduleHeight: 4),
data: barcodeNumber,
margin: const EdgeInsets.all(10),
height: constraints.maxWidth / 3,
);
},
),
),
],
), ),
), ),
), ],
], ),
), ),
), ),
); );

View File

@ -29,6 +29,7 @@ class _RestaurantsListState extends State<RestaurantsList> {
itemCount: restaurants.length, itemCount: restaurants.length,
cacheExtent: 10000, cacheExtent: 10000,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
prototypeItem: Padding( prototypeItem: Padding(
padding: const EdgeInsets.only(right: 15), padding: const EdgeInsets.only(right: 15),
child: RestaurantCard(restaurant: restaurants.first), child: RestaurantCard(restaurant: restaurants.first),