diff --git a/app/lib/widget/fade_out_list.dart b/app/lib/widget/fade_out_list.dart new file mode 100644 index 00000000..2ea75176 --- /dev/null +++ b/app/lib/widget/fade_out_list.dart @@ -0,0 +1,118 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class FadeOutListContainer extends StatefulWidget { + const FadeOutListContainer({ + super.key, + required this.scrollController, + required this.child, + }); + + @override + State createState() => _FadeOutListContainerState(); + + final ScrollController scrollController; + final Widget child; +} + +class _FadeOutListContainerState extends State { + @override + void initState() { + super.initState(); + widget.scrollController.addListener(_onScrollEvent); + _ensureUpdateButtonScroll(); + } + + @override + void dispose() { + widget.scrollController.removeListener(_onScrollEvent); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ShaderMask( + shaderCallback: (rect) { + final colors = []; + final stops = []; + if (_hasLeftContent) { + colors.addAll([Colors.white, Colors.transparent]); + stops.addAll([0, .1]); + } else { + colors.add(Colors.transparent); + stops.add(0); + } + if (_hasRightContent) { + colors.addAll([Colors.transparent, Colors.white]); + stops.addAll([.9, 1]); + } else { + colors.add(Colors.transparent); + stops.add(1); + } + return LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: colors, + stops: stops, + ).createShader(rect); + }, + blendMode: BlendMode.dstOut, + child: widget.child, + ); + } + + void _onScrollEvent() { + _updateButtonScroll(widget.scrollController.position); + } + + bool _updateButtonScroll(ScrollPosition pos) { + if (!pos.hasContentDimensions || !pos.hasPixels) { + return false; + } + if (pos.pixels <= pos.minScrollExtent) { + if (_hasLeftContent) { + setState(() { + _hasLeftContent = false; + }); + } + } else { + if (!_hasLeftContent) { + setState(() { + _hasLeftContent = true; + }); + } + } + if (pos.pixels >= pos.maxScrollExtent) { + if (_hasRightContent) { + setState(() { + _hasRightContent = false; + }); + } + } else { + if (!_hasRightContent) { + setState(() { + _hasRightContent = true; + }); + } + } + _hasFirstScrollUpdate = true; + return true; + } + + void _ensureUpdateButtonScroll() { + if (_hasFirstScrollUpdate || !mounted) { + return; + } + if (widget.scrollController.hasClients) { + if (_updateButtonScroll(widget.scrollController.position)) { + return; + } + } + Timer(const Duration(milliseconds: 100), _ensureUpdateButtonScroll); + } + + var _hasFirstScrollUpdate = false; + var _hasLeftContent = false; + var _hasRightContent = false; +} diff --git a/app/lib/widget/home_collections.dart b/app/lib/widget/home_collections.dart index 4f884165..0cb3727a 100644 --- a/app/lib/widget/home_collections.dart +++ b/app/lib/widget/home_collections.dart @@ -34,6 +34,7 @@ import 'package:nc_photos/widget/archive_browser.dart'; import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/collection_grid_item.dart'; import 'package:nc_photos/widget/enhanced_photo_browser.dart'; +import 'package:nc_photos/widget/fade_out_list.dart'; import 'package:nc_photos/widget/handler/double_tap_exit_handler.dart'; import 'package:nc_photos/widget/home_app_bar.dart'; import 'package:nc_photos/widget/navigation_bar_blur_filter.dart'; diff --git a/app/lib/widget/home_collections/navigation_bar.dart b/app/lib/widget/home_collections/navigation_bar.dart index 591ba87d..3dee99cd 100644 --- a/app/lib/widget/home_collections/navigation_bar.dart +++ b/app/lib/widget/home_collections/navigation_bar.dart @@ -20,15 +20,6 @@ class _NavigationBar extends StatefulWidget { } class _NavigationBarState extends State<_NavigationBar> { - @override - void initState() { - super.initState(); - _scrollController = ScrollController(); - _scrollController - .addListener(() => _updateButtonScroll(_scrollController.position)); - _ensureUpdateButtonScroll(); - } - @override void dispose() { _scrollController.dispose(); @@ -42,32 +33,8 @@ class _NavigationBarState extends State<_NavigationBar> { child: Row( children: [ Expanded( - child: ShaderMask( - shaderCallback: (rect) { - final colors = []; - final stops = []; - if (_hasLeftContent) { - colors.addAll([Colors.white, Colors.transparent]); - stops.addAll([0, .1]); - } else { - colors.add(Colors.transparent); - stops.add(0); - } - if (_hasRightContent) { - colors.addAll([Colors.transparent, Colors.white]); - stops.addAll([.9, 1]); - } else { - colors.add(Colors.transparent); - stops.add(1); - } - return LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: colors, - stops: stops, - ).createShader(rect); - }, - blendMode: BlendMode.dstOut, + child: FadeOutListContainer( + scrollController: _scrollController, child: _BlocSelector( selector: (state) => state.navBarButtons, builder: (context, navBarButtons) { @@ -112,56 +79,7 @@ class _NavigationBarState extends State<_NavigationBar> { } } - bool _updateButtonScroll(ScrollPosition pos) { - if (!pos.hasContentDimensions || !pos.hasPixels) { - return false; - } - if (pos.pixels <= pos.minScrollExtent) { - if (_hasLeftContent) { - setState(() { - _hasLeftContent = false; - }); - } - } else { - if (!_hasLeftContent) { - setState(() { - _hasLeftContent = true; - }); - } - } - if (pos.pixels >= pos.maxScrollExtent) { - if (_hasRightContent) { - setState(() { - _hasRightContent = false; - }); - } - } else { - if (!_hasRightContent) { - setState(() { - _hasRightContent = true; - }); - } - } - _hasFirstScrollUpdate = true; - return true; - } - - void _ensureUpdateButtonScroll() { - if (_hasFirstScrollUpdate || !mounted) { - return; - } - if (_scrollController.hasClients) { - if (_updateButtonScroll(_scrollController.position)) { - return; - } - } - Timer(const Duration(milliseconds: 100), _ensureUpdateButtonScroll); - } - - late final ScrollController _scrollController; - var _hasFirstScrollUpdate = false; - var _hasLeftContent = false; - var _hasRightContent = false; + final _scrollController = ScrollController(); } class _NavBarButtonIndicator extends StatelessWidget { diff --git a/app/lib/widget/settings/collections_nav_bar/view.dart b/app/lib/widget/settings/collections_nav_bar/view.dart index fe8d7aed..cb26e583 100644 --- a/app/lib/widget/settings/collections_nav_bar/view.dart +++ b/app/lib/widget/settings/collections_nav_bar/view.dart @@ -1,8 +1,19 @@ part of '../collections_nav_bar_settings.dart'; -class _DemoView extends StatelessWidget { +class _DemoView extends StatefulWidget { const _DemoView(); + @override + State createState() => _DemoViewState(); +} + +class _DemoViewState extends State<_DemoView> { + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return _BlocSelector( @@ -13,38 +24,42 @@ class _DemoView extends StatelessWidget { child: Row( children: [ Expanded( - child: ListView.builder( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.only(left: 16 - 6), - itemCount: buttons.length, - itemBuilder: (context, i) { - final btn = buttons[i]; - return my.Draggable( - data: btn.type, - feedback: _CandidateButtonDelegate(btn.type), - onDropBefore: (data) { - context.addEvent(_MoveButton.before( - which: data, - target: btn.type, - )); - }, - onDropAfter: (data) { - context.addEvent(_MoveButton.after( - which: data, - target: btn.type, - )); - }, - child: Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 6), - child: _DemoButtonDelegate( - btn.type, - isMinimized: btn.isMinimized, + child: FadeOutListContainer( + scrollController: _scrollController, + child: ListView.builder( + controller: _scrollController, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.only(left: 16 - 6), + itemCount: buttons.length, + itemBuilder: (context, i) { + final btn = buttons[i]; + return my.Draggable( + data: btn.type, + feedback: _CandidateButtonDelegate(btn.type), + onDropBefore: (data) { + context.addEvent(_MoveButton.before( + which: data, + target: btn.type, + )); + }, + onDropAfter: (data) { + context.addEvent(_MoveButton.after( + which: data, + target: btn.type, + )); + }, + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: _DemoButtonDelegate( + btn.type, + isMinimized: btn.isMinimized, + ), ), ), - ), - ); - }, + ); + }, + ), ), ), const SizedBox(width: 8), @@ -81,6 +96,8 @@ class _DemoView extends StatelessWidget { }, ); } + + final _scrollController = ScrollController(); } class _DemoButtonDelegate extends StatelessWidget { diff --git a/app/lib/widget/settings/collections_nav_bar_settings.dart b/app/lib/widget/settings/collections_nav_bar_settings.dart index 5f64820c..834f85d2 100644 --- a/app/lib/widget/settings/collections_nav_bar_settings.dart +++ b/app/lib/widget/settings/collections_nav_bar_settings.dart @@ -12,6 +12,7 @@ import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/widget/draggable.dart' as my; +import 'package:nc_photos/widget/fade_out_list.dart'; import 'package:nc_photos/widget/home_collections.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:np_codegen/np_codegen.dart';