library draggable_home; import 'package:fikoyt_mesaici/theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; class DraggableHome extends StatefulWidget { @override _DraggableHomeState createState() => _DraggableHomeState(); /// 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; /// This will create DraggableHome. const DraggableHome({ 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.bodyMaxWidth = 460, }); } class _DraggableHomeState extends State { final BehaviorSubject isFullyExpanded = BehaviorSubject.seeded(false); final BehaviorSubject isFullyCollapsed = BehaviorSubject.seeded(false); @override void dispose() { isFullyExpanded.close(); isFullyCollapsed.close(); 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 = /*MediaQuery.of(context).size.height + */ widget.headerExpandedHeight; final double fullyExpandedHeight = MediaQuery.of(context).size.height * (widget.stretchMaxHeight); return Scaffold( backgroundColor: widget.backgroundColor ?? FikoTheme.of(context).uygulamaArkaPlanRenk, drawer: widget.drawer, body: NotificationListener( onNotification: (notification) { if (notification.metrics.axis == Axis.vertical) { // isFullyCollapsed if ((isFullyExpanded.value) && notification.metrics.extentBefore > 100) { isFullyExpanded.add(false); } //isFullyCollapsed 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: sliver(context, appBarHeight, fullyExpandedHeight, expandedHeight, topPadding), ), bottomSheet: widget.bottomSheet, bottomNavigationBar: widget.bottomNavigationBar, floatingActionButton: widget.floatingActionButton, floatingActionButtonLocation: widget.floatingActionButtonLocation, floatingActionButtonAnimator: widget.floatingActionButtonAnimator, ); } CustomScrollView sliver( BuildContext context, double appBarHeight, double fullyExpandedHeight, double expandedHeight, double topPadding, ) { return CustomScrollView( physics: widget.physics ?? const BouncingScrollPhysics(), controller: widget.scrollController, slivers: [ StreamBuilder>( stream: CombineLatestStream.list([ isFullyCollapsed.stream, isFullyExpanded.stream, ]), builder: (BuildContext context, AsyncSnapshot> snapshot) { final List streams = (snapshot.data ?? [false, false]); final bool fullyCollapsed = streams[0]; final bool fullyExpanded = streams[1]; return SliverAppBar( backgroundColor: !fullyCollapsed ? widget.backgroundColor : widget.appBarColor, leading: widget.alwaysShowLeadingAndAction ? widget.leading : !fullyCollapsed ? const SizedBox() : widget.leading, actions: widget.alwaysShowLeadingAndAction ? widget.actions : !fullyCollapsed ? [] : widget.actions, elevation: 0, pinned: true, stretch: true, centerTitle: widget.centerTitle, title: widget.alwaysShowTitle ? widget.title : AnimatedOpacity( opacity: fullyCollapsed ? 1 : 0, duration: const Duration(milliseconds: 100), child: widget.title, ), collapsedHeight: appBarHeight, expandedHeight: fullyExpanded ? fullyExpandedHeight : expandedHeight, flexibleSpace: Stack( children: [ FlexibleSpaceBar( background: Container( margin: EdgeInsets.only(bottom: 0.2, top: MediaQuery.of(context).padding.top), child: fullyExpanded ? (widget.expandedBody ?? const SizedBox()) : widget.headerWidget ?? const SizedBox(), ), ), Positioned( bottom: -1, left: 0, right: 0, child: roundedCorner(context), ), Positioned( bottom: 0 + widget.curvedBodyRadius, child: AnimatedContainer( padding: const EdgeInsets.only(left: 0, right: 0), curve: Curves.easeInOutCirc, duration: const Duration(milliseconds: 100), // height: fullyCollapsed // ? 0 // : fullyExpanded // ? 0 // : 22, width: MediaQuery.of(context).size.width, child: fullyCollapsed ? const SizedBox() : fullyExpanded ? const SizedBox() : widget.headerBottomBar ?? IgnorePointer( child: SizedBox( height: 20, child: Stack( children: [ // Gradient gölge Positioned.fill( child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context).shadowColor, // Colors.black.withOpacity(0.1), Colors.transparent, ], begin: Alignment.bottomCenter, end: Alignment.topCenter, ), ), ), ), ], ), ), ), ), ) ], ), stretchTriggerOffset: widget.stretchTriggerOffset, onStretchTrigger: () async { // if (!fullyExpanded) isFullyExpanded.add(true); widget.onStretchTrigger != null ? widget.onStretchTrigger!() : null; }, ); }, ), sliverList(context, appBarHeight + topPadding), /** */ SliverFillRemaining( hasScrollBody: true, fillOverscroll: true, child: Padding( padding: EdgeInsets.only(top: appBarHeight), child: widget.sliverAltKisim ?? Container( color: widget.backgroundColor, // Colors.transparent, // child: SizedBox( // height: appBarHeight + topPadding, // ), 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, // width: widget.logoWidth * cardWidth, ), ), ), ), ), // Container( // color: widget.backgroundColor, // Colors.transparent, // // child: SizedBox( // // height: appBarHeight + topPadding, // // ), // 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, // // width: widget.logoWidth * cardWidth, // ), // ), // ), // ), ), ], ); } Container roundedCorner(BuildContext context) { return Container( height: widget.curvedBodyRadius, decoration: BoxDecoration( color: widget.backgroundColor ?? FikoTheme.of(context).uygulamaArkaPlanRenk, borderRadius: BorderRadius.vertical( top: Radius.circular(widget.curvedBodyRadius), ), ), ); } SliverList sliverList(BuildContext context, double topHeight) { // final double bottomPadding = // widget.bottomNavigationBar == null ? 0 : kBottomNavigationBarHeight; return SliverList( delegate: SliverChildListDelegate( [ // Stack( // children: [ Container( // height: MediaQuery.of(context).size.height - // topHeight - // bottomPadding, color: Colors.transparent, // widget.backgroundColor ?? FikoTheme.of(context).uygulamaArkaPlan, child: Column( children: [ expandedUpArrow(), // SizedBox( // height: 20, // child: Stack( // children: [ // // Gradient gölge // Positioned.fill( // child: Container( // decoration: BoxDecoration( // gradient: LinearGradient( // colors: [ // Colors.black.withOpacity(0.1), // Colors.transparent, // ], // begin: Alignment.topCenter, // end: Alignment.bottomCenter, // ), // ), // ), // ), // ], // ), // ), //Body Container( // height: MediaQuery.of(context).size.height - // topHeight - // bottomPadding, color: widget.bodyColor, child: Container( alignment: Alignment.topCenter, constraints: BoxConstraints(maxWidth: widget.bodyMaxWidth ?? 0), child: Column( children: [ ...widget.body, ], )), ), IgnorePointer( child: SizedBox( height: 20, child: Stack( children: [ // Gradient gölge Positioned.fill( child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Theme.of(context).shadowColor, // Colors.black.withOpacity(0.1), Colors.transparent, ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ), ], ), ), ), SizedBox( height: MediaQuery.of(context).padding.bottom, ), ], ), ), // ], // ), ], ), ); } StreamBuilder expandedUpArrow() { return StreamBuilder( stream: isFullyExpanded.stream, builder: (context, snapshot) { return AnimatedContainer( duration: const Duration(milliseconds: 500), height: (snapshot.data ?? false) ? 25 : 0, width: MediaQuery.of(context).size.width, child: Center( child: Icon( Icons.keyboard_arrow_up_rounded, color: (snapshot.data ?? false) ? null : Colors.transparent, ), ), ); }, ); } }