mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Remove arrow animation (better app pack UX incoming)
This commit is contained in:
@@ -53,91 +53,25 @@ class AppPackContainer extends HookConsumerWidget {
|
||||
orElse: () => packs.first,
|
||||
);
|
||||
|
||||
// Scroll controller and hint visibility state
|
||||
final scrollController = useScrollController();
|
||||
final showRightHint = useState(true);
|
||||
final hasScrolledOnce = useState(false);
|
||||
|
||||
// Listen to scroll position to hide hint when near end
|
||||
useEffect(() {
|
||||
void listener() {
|
||||
if (!scrollController.hasClients) return;
|
||||
final maxScroll = scrollController.position.maxScrollExtent;
|
||||
final currentScroll = scrollController.offset;
|
||||
|
||||
// Hide hint when scrolled past 20px or near the end
|
||||
if (currentScroll > 20) {
|
||||
hasScrolledOnce.value = true;
|
||||
}
|
||||
showRightHint.value =
|
||||
!hasScrolledOnce.value && currentScroll < maxScroll - 20;
|
||||
}
|
||||
|
||||
// Check initial state after first frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients) {
|
||||
final maxScroll = scrollController.position.maxScrollExtent;
|
||||
// Hide hint if content doesn't overflow
|
||||
showRightHint.value = maxScroll > 20;
|
||||
}
|
||||
});
|
||||
|
||||
scrollController.addListener(listener);
|
||||
return () => scrollController.removeListener(listener);
|
||||
}, [scrollController]);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 48,
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(left: 12, right: 52),
|
||||
child: Row(
|
||||
children: packs
|
||||
.map(
|
||||
(pack) => _PackPill(
|
||||
pack: pack,
|
||||
isSelected: selectedId.value == pack.id,
|
||||
onTap: () => selectedId.value = pack.id,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
// Right fade gradient with animated chevron
|
||||
if (showRightHint.value)
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 56,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
AppColors.darkBackground,
|
||||
AppColors.darkBackground,
|
||||
],
|
||||
stops: [0.0, 0.5, 1.0],
|
||||
),
|
||||
),
|
||||
child: const Align(
|
||||
alignment: Alignment(0.6, -0.1),
|
||||
child: _AnimatedScrollHint(),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: packs
|
||||
.map(
|
||||
(pack) => _PackPill(
|
||||
pack: pack,
|
||||
isSelected: selectedId.value == pack.id,
|
||||
onTap: () => selectedId.value = pack.id,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -205,81 +139,6 @@ class _SkeletonPill extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Animated chevron hint for horizontal scroll discovery
|
||||
class _AnimatedScrollHint extends StatefulWidget {
|
||||
const _AnimatedScrollHint();
|
||||
|
||||
@override
|
||||
State<_AnimatedScrollHint> createState() => _AnimatedScrollHintState();
|
||||
}
|
||||
|
||||
class _AnimatedScrollHintState extends State<_AnimatedScrollHint>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _offsetAnimation;
|
||||
late Animation<double> _opacityAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 900),
|
||||
vsync: this,
|
||||
)..repeat(reverse: true);
|
||||
|
||||
_offsetAnimation = Tween<double>(
|
||||
begin: 0,
|
||||
end: 6,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||
|
||||
_opacityAnimation = Tween<double>(
|
||||
begin: 0.7,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) => Transform.translate(
|
||||
offset: Offset(_offsetAnimation.value, 0),
|
||||
child: Opacity(
|
||||
opacity: _opacityAnimation.value,
|
||||
// Stack icons with slight offsets to create bold effect
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0.5,
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
color: Colors.white.withValues(alpha: 0.7),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Individual pack pill - watches its own author reactively
|
||||
class _PackPill extends ConsumerWidget {
|
||||
const _PackPill({
|
||||
|
||||
Reference in New Issue
Block a user