library; import 'package:fikoyt_ui/fikoyt_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; class FikoYTSliverApp extends StatefulWidget { /// Leading: A widget to display before the toolbar's title. final Widget? leading; /// Title: A Widget to display title in AppBar final Widget title; /// Center Title: Allows toggling of title from the center. By default title is in the center. final bool centerTitle; /// Action: A list of Widgets to display in a row after the title widget. final List? actions; /// Always Show Leading And Action : This make Leading and Action always visible. Default value is false. final bool alwaysShowLeadingAndAction; /// Always Show Title : This make Title always visible. Default value is false. final bool alwaysShowTitle; /// Drawer: Drawers are typically used with the Scaffold.drawer property. final Widget? drawer; /// Header Expanded Height : Height of the header widget. The height is a double between 0.0 and 1.0. The default value of height is 0.35 and should be less than stretchMaxHeigh final double headerExpandedHeight; /// Header Widget: A widget to display Header above body. final Widget? headerWidget; /// headerBottomBar: AppBar or toolBar like widget just above the body. final Widget? headerBottomBar; /// backgroundColor: The color of the Material widget that underlies the entire DraggableHome body. final Color? backgroundColor; /// appBarColor: The color of the scaffold app bar. final Color? appBarColor; /// curvedBodyRadius: Creates a border top left and top right radius of body, Default radius of the body is 20.0. For no radius simply set value to 0. final double curvedBodyRadius; /// body: A widget to Body final List body; final double? bodyMaxWidth; final Color? bodyColor; /// fullyStretchable: Allows toggling of fully expand draggability of the DraggableHome. Set this to true to allow the user to fully expand the header. final bool fullyStretchable; /// stretchTriggerOffset: The offset of overscroll required to fully expand the header. final double stretchTriggerOffset; /// expandedBody: A widget to display when fully expanded as header or expandedBody above body. final Widget? expandedBody; /// stretchMaxHeight: Height of the expandedBody widget. The height is a double between 0.0 and 0.95. The default value of height is 0.9 and should be greater than headerExpandedHeight final double stretchMaxHeight; /// floatingActionButton: An object that defines a position for the FloatingActionButton based on the Scaffold's ScaffoldPrelayoutGeometry. final Widget? floatingActionButton; /// bottomSheet: A persistent bottom sheet shows information that supplements the primary content of the app. A persistent bottom sheet remains visible even when the user interacts with other parts of the app. final Widget? bottomSheet; /// bottomNavigationBarHeight: This is requires when using custom height to adjust body height. This make no effect on bottomNavigationBar. final double? bottomNavigationBarHeight; /// bottomNavigationBar: Snack bars slide from underneath the bottom navigation bar while bottom sheets are stacked on top. final Widget? bottomNavigationBar; /// floatingActionButtonLocation: An object that defines a position for the FloatingActionButton based on the Scaffold's ScaffoldPrelayoutGeometry. final FloatingActionButtonLocation? floatingActionButtonLocation; /// floatingActionButtonAnimator: Provider of animations to move the FloatingActionButton between FloatingActionButtonLocations. final FloatingActionButtonAnimator? floatingActionButtonAnimator; /// physics: How the scroll view should respond to user input. For example, determines how the scroll view continues to animate after the user stops dragging the scroll view. final ScrollPhysics? physics; /// scrollController: An object that can be us /// ed to control the position to which this scroll view is scrolled. final ScrollController? scrollController; final AsyncCallback? onStretchTrigger; /// Header Widget: A widget to display Header above body. final Widget? sliverAltKisim; final bool sliverAltKisimGoster; final bool golgeGoster; /// This will create DraggableHome. const FikoYTSliverApp({ super.key, this.leading, required this.title, this.centerTitle = true, this.actions, this.alwaysShowLeadingAndAction = false, this.alwaysShowTitle = false, this.headerExpandedHeight = 0.35, this.headerWidget, this.headerBottomBar, this.backgroundColor, this.appBarColor, this.curvedBodyRadius = 0, required this.body, this.bodyColor, this.drawer, this.fullyStretchable = false, this.stretchTriggerOffset = 200, this.expandedBody, this.stretchMaxHeight = 0.9, this.bottomSheet, this.bottomNavigationBarHeight = kBottomNavigationBarHeight, this.bottomNavigationBar, this.floatingActionButton, this.floatingActionButtonLocation, this.floatingActionButtonAnimator, this.physics, this.onStretchTrigger, this.scrollController, this.sliverAltKisim, this.sliverAltKisimGoster = true, this.golgeGoster = true, this.bodyMaxWidth = 460, }); @override State createState() => _FikoYTSliverAppState(); } class _FikoYTSliverAppState extends State { final BehaviorSubject _isFullyExpanded = BehaviorSubject.seeded(false); final BehaviorSubject _isFullyCollapsed = BehaviorSubject.seeded(false); late final ValueNotifier _isCollapsedNotifier; late final ValueNotifier _isExpandedNotifier; @override void initState() { super.initState(); _isCollapsedNotifier = ValueNotifier(false); _isExpandedNotifier = ValueNotifier(false); _isFullyCollapsed.stream.listen((isCollapsed) { _isCollapsedNotifier.value = isCollapsed; }); _isFullyExpanded.stream.listen((isExpanded) { _isExpandedNotifier.value = isExpanded; }); } @override void dispose() { _isFullyExpanded.close(); _isFullyCollapsed.close(); _isCollapsedNotifier.dispose(); _isExpandedNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final double appBarHeight = AppBar().preferredSize.height + widget.curvedBodyRadius; final double topPadding = MediaQuery.of(context).padding.top; final double expandedHeight = widget.headerExpandedHeight; final double fullyExpandedHeight = MediaQuery.of(context).size.height * (widget.stretchMaxHeight); return SafeArea( child: Scaffold( drawer: widget.drawer, body: Stack( children: [ // NotificationListener( // onNotification: (notification) { // if (notification.metrics.axis == Axis.vertical) { // if ((_isFullyExpanded.value) && notification.metrics.extentBefore > 100) { // _isFullyExpanded.add(false); // } // if (notification.metrics.extentBefore > expandedHeight - AppBar().preferredSize.height - 40) { // if (!(_isFullyCollapsed.value)) _isFullyCollapsed.add(true); // } else { // if ((_isFullyCollapsed.value)) _isFullyCollapsed.add(false); // } // } // return false; // }, // child: _buildSliver(context, appBarHeight, fullyExpandedHeight, expandedHeight, topPadding), // ), if (widget.bottomNavigationBar != null) Positioned(bottom: 0, left: 0, right: 0, child: Center(child: widget.bottomNavigationBar!)), ], ), bottomSheet: widget.bottomSheet, floatingActionButton: widget.floatingActionButton, floatingActionButtonLocation: widget.floatingActionButtonLocation, floatingActionButtonAnimator: widget.floatingActionButtonAnimator, backgroundColor: widget.backgroundColor, ), ); } Widget _buildSliver( BuildContext context, double appBarHeight, double fullyExpandedHeight, double expandedHeight, double topPadding, ) { return CustomScrollView( physics: widget.physics ?? const BouncingScrollPhysics(), controller: widget.scrollController, slivers: [ SliverAppBar( backgroundColor: _isCollapsedNotifier.value ? widget.appBarColor : widget.backgroundColor, leading: widget.alwaysShowLeadingAndAction ? widget.leading : !_isCollapsedNotifier.value ? const SizedBox() : widget.leading, actions: widget.alwaysShowLeadingAndAction ? widget.actions : !_isCollapsedNotifier.value ? [] : widget.actions, elevation: 10, forceElevated: true, scrolledUnderElevation: 10.0, pinned: true, stretch: true, centerTitle: widget.centerTitle, title: widget.alwaysShowTitle ? widget.title : AnimatedOpacity( opacity: _isCollapsedNotifier.value ? 1 : 0, duration: const Duration(milliseconds: 100), child: widget.title, ), collapsedHeight: appBarHeight, expandedHeight: _isExpandedNotifier.value ? fullyExpandedHeight : expandedHeight, flexibleSpace: _buildFlexibleSpace(context, fullyExpandedHeight), stretchTriggerOffset: widget.stretchTriggerOffset, onStretchTrigger: () async { widget.onStretchTrigger?.call(); }, ), _buildSliverList(context, appBarHeight + topPadding), if (widget.sliverAltKisimGoster) _buildSliverAltKisim(context, appBarHeight), ], ); } FlexibleSpaceBar _buildFlexibleSpace(BuildContext context, double fullyExpandedHeight) { return FlexibleSpaceBar( background: Stack( children: [ Container( margin: EdgeInsets.only(bottom: 0.2, top: MediaQuery.of(context).padding.top), child: _isExpandedNotifier.value ? (widget.expandedBody ?? const SizedBox()) : widget.headerWidget ?? const SizedBox(), ), Positioned( bottom: -1, left: 0, right: 0, child: _roundedCorner(context), ), Positioned( bottom: widget.curvedBodyRadius, child: !_isCollapsedNotifier.value && !_isExpandedNotifier.value ? SizedBox(width: MediaQuery.of(context).size.width,child: (widget.headerBottomBar ?? (widget.golgeGoster ? _buildHeaderBottomBarShadow(context) : const SizedBox()))) : const SizedBox(), ) ], )); } Container _roundedCorner(BuildContext context) { return Container( height: widget.curvedBodyRadius, decoration: BoxDecoration( color: widget.backgroundColor ?? Theme.of(context).extension()?.backgroundColor, borderRadius: BorderRadius.vertical( top: Radius.circular(widget.curvedBodyRadius), ), ), ); } IgnorePointer _buildHeaderBottomBarShadow(BuildContext context) { return IgnorePointer( child: const SizedBox( height: 20, child: Stack( children: [ Positioned.fill( child: HeaderBottomBarShadow(), ), ], ), )); } Widget _buildSliverAltKisim(BuildContext context, double appBarHeight) { return SliverFillRemaining( hasScrollBody: false, // Changed to false fillOverscroll: true, child: Padding( padding: EdgeInsets.only(top: appBarHeight), child: widget.sliverAltKisim ?? Container( color: widget.backgroundColor, child: const Align( alignment: Alignment.center, child: Padding( padding: EdgeInsets.all(50), child: Image( image: AssetImage('assets/core/icons/fikoyt.png'), filterQuality: FilterQuality.high, height: 72, ), ), ), ), ), ); } SliverList _buildSliverList(BuildContext context, double topHeight) { return SliverList( delegate: SliverChildListDelegate([ Container( color: Colors.transparent, child: Column( children: [ // _expandedUpArrow(), Container( color: widget.bodyColor, alignment: Alignment.topCenter, constraints: BoxConstraints(maxWidth: widget.bodyMaxWidth ?? 0), child: Column( children: widget.body, ), ), if (widget.golgeGoster) _buildBodyShadow(context), SizedBox( height: MediaQuery.of(context).padding.bottom, ), ], ), ), ]), ); } IgnorePointer _buildBodyShadow(BuildContext context) { return IgnorePointer( child: const SizedBox( height: 20, child: Stack( children: [ Positioned.fill( child: BodyBottomShadow(), ), ], ), )); } // Widget _expandedUpArrow() { // return AnimatedContainer( // duration: const Duration(milliseconds: 500), // height: _isExpandedNotifier.value ? 25 : 0, // width: MediaQuery.of(context).size.width, // child: Center( // child: Icon( // Icons.keyboard_arrow_up_rounded, // color: _isExpandedNotifier.value ? null : Colors.transparent, // ), // ), // ); // } } class BodyBottomShadow extends StatelessWidget { const BodyBottomShadow({super.key}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context).shadowColor, Colors.transparent, ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ); } } class HeaderBottomBarShadow extends StatelessWidget { const HeaderBottomBarShadow({super.key}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context).shadowColor, Colors.transparent, ], begin: Alignment.bottomCenter, end: Alignment.topCenter, ), ), ); } }