import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_swiper/flutter_swiper.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:twong/api/index.dart'; import 'package:twong/utils/index.dart'; import 'package:twong/config/style.dart'; import 'package:twong/router/index.dart'; import 'package:twong/models/index.dart'; import 'package:twong/widgets/future_widget.dart'; import 'package:twong/pages/product/product_attr.dart'; import 'package:twong/widgets/share_bar.dart'; class ProductDetailsPage extends StatefulWidget { final dynamic id; ProductDetailsPage (this.id, {Key key}): super(key: key); @override State createState() { return _ProductDetailsPageState(); } } class _ProductDetailsPageState extends State { Details _details; IconData _likeIcon = Icons.favorite_border; @override void initState() { super.initState(); } void _onAddCart () { showModalBottomSheet( context: context, shape: RoundedRectangleBorder(borderRadius: BorderRadius.only( topLeft: Radius.circular(10.px), topRight: Radius.circular(10.px) )), builder: (BuildContext context) { return ProductAttrPage(_details, isCart: true); } ); } void _onBuy () { showModalBottomSheet( context: context, shape: RoundedRectangleBorder(borderRadius: BorderRadius.only( topLeft: Radius.circular(10.px), topRight: Radius.circular(10.px) )), builder: (BuildContext context) { return ProductAttrPage(_details); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: FutureWidget(Network.inst.getDetail, _buildBody, data: widget.id), bottomNavigationBar: SafeArea( child: Container( height: 60.px, color: Colors.white, child: Flex( direction: Axis.horizontal, children: [ Row( children: [ GestureDetector( onTap: () { Navigator.pushNamed( context, RouteNames.message, arguments: false); }, child: Container( width: 60.px, padding: EdgeInsets.only( left: 16.px, right: 12.px, top: 12.px), child: Column( children: [ Icon(Icons.message), Text('客服', style: TextStyle(fontSize: 10.px),) ], ), ) ), GestureDetector( onTap: () { Navigator.pushNamed( context, RouteNames.cart, arguments: true); }, child: Container( padding: EdgeInsets.only( left: 12.px, right: 12.px, top: 12.px), child: Column( children: [ Icon(Icons.shopping_cart), Text('购物车', style: TextStyle(fontSize: 10.px),) ], ), ) ), ], ), Expanded(child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Container( padding: EdgeInsets.all(6.px), child: FlatButton( onPressed: _onAddCart, color: Colors.black, shape: StadiumBorder(), child: Text('加入购物车', style: TextStyle(color: Colors.white)), ) ), ), Expanded( child: Container( padding: EdgeInsets.all(6.px), child: FlatButton( onPressed: _onBuy, color: Colors.red, shape: StadiumBorder(), child: Text('立即购买', style: TextStyle(color: Colors.white)), ) ), ), ], )), ], ), ), ) ); } Widget _buildBody(dynamic data) { _details = data; return CustomScrollView( physics: ClampingScrollPhysics(), slivers: [ SliverPersistentHeader( pinned: true, delegate: SliverCustomHeaderDelegate( paddingTop: MediaQuery.of(context).padding.top, data: _details, likeClick: _likeClick, likeIcon: _likeIcon), ), SliverToBoxAdapter( child: ProductContent(_details), ) ], ); } void _likeClick() async { if(_likeIcon == Icons.favorite_border) { setState(() { _likeIcon = Icons.favorite; }); } else { setState(() { _likeIcon = Icons.favorite_border; }); } } } class SliverCustomHeaderDelegate extends SliverPersistentHeaderDelegate { final double collapsedHeight = 40.px; final double expandedHeight = 400.px; final double paddingTop; final Details data; final IconData likeIcon; final Function likeClick; final Color topColor = DColors.Main; int tabIdx = 0; String statusBarMode = 'dark'; final Color tColor = Color.fromARGB(168, DColors.Main.red, DColors.Main.green, DColors.Main.blue); SliverCustomHeaderDelegate({this.paddingTop, this.data, this.likeClick, this.likeIcon}); @override double get maxExtent => this.expandedHeight; @override double get minExtent => this.collapsedHeight + this.paddingTop; @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true; void updateStatusBarBrightness(shrinkOffset) { if (shrinkOffset > 50.px && this.statusBarMode == 'light') { this.statusBarMode = 'dark'; SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarBrightness: Brightness.dark, statusBarIconBrightness: Brightness.dark, )); } else if (shrinkOffset <= 50.px && this.statusBarMode == 'dark') { this.statusBarMode = 'light'; SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.light, )); } } Color makeStickyHeaderBgColor(shrinkOffset) { final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255) .clamp(0, 255) .toInt(); return Color.fromARGB(alpha, tColor.red, tColor.green, tColor.blue); } Color makeStickyHeaderTextColor(shrinkOffset, isIcon) { if (shrinkOffset <= 50.px) { return isIcon ? DColors.Main : Colors.transparent; } else { final int alpha = (shrinkOffset / (this.maxExtent - this.minExtent) * 255) .clamp(0, 255).toInt(); return Color.fromARGB(alpha, 255, 255, 255); } } Widget _buildSlider(BuildContext context, int index) { return CachedNetworkImage( fit: BoxFit.cover, imageUrl: data.storeInfo.slider_image[index], placeholder: (BuildContext context, String str) { return Center(child: Utils.loadingWidget); }, ); } @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { this.updateStatusBarBrightness(shrinkOffset); var images = data.storeInfo.slider_image; return Container( height: this.maxExtent, width: MediaQuery .of(context) .size .width, child: Stack( fit: StackFit.expand, children: [ Container( height: 400.px, child: Swiper( autoplay: true, itemCount: images.length, itemBuilder: _buildSlider, pagination: SwiperPagination(), autoplayDisableOnInteraction: true, onTap: (index) { Utils.showPhoto(index: index, images: images); } ) ), Positioned(left: 0, right: 0, top: 0, child: Container( color: this.makeStickyHeaderBgColor(shrinkOffset), child: SafeArea( bottom: false, child: Container( height: this.collapsedHeight, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( margin: EdgeInsets.only(left: 10.px), child: InkWell( onTap: () => Navigator.pop(context), child: CircleAvatar( radius: 15.px, backgroundColor: tColor, child: Icon( Icons.chevron_left, color: Colors.white, size: 30.px), ), ) ), Expanded(child: _buildHeaderMenu(shrinkOffset)), AnimatedSwitcher( transitionBuilder: (child, anim) => ScaleTransition(child: child, scale: anim), duration: Duration(milliseconds: 300), child: Container( key: ValueKey(likeIcon), padding: EdgeInsets.only(left: 16.px, right: 10.px), child: InkWell( onTap: likeClick, child: CircleAvatar( radius: 15.px, backgroundColor: tColor, child: Icon( likeIcon, color: Colors.white, size: 21.px), ), ) ), ), Container( padding: EdgeInsets.only(left: 16.px, right: 10.px), child: InkWell( onTap: _showShare, child: CircleAvatar( radius: 15, backgroundColor: tColor, child: Icon(Icons.share, color: Colors.white, size: 21.px), ), ) ), ], ), ), ), ), ), ], ), ); } void _showShare() { showModalBottomSheet( context: Cache.context, shape: RoundedRectangleBorder(borderRadius: BorderRadius.only( topLeft: Radius.circular(10.px), topRight: Radius.circular(10.px) )), builder: (BuildContext context) { return ShareBar(data); }); } Widget _buildHeaderMenu(double shrinkOffset) { var textColor = this.makeStickyHeaderTextColor(shrinkOffset, false); return Container( margin: EdgeInsets.only(left: 20.px, right: 20.px), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: () { this.tabIdx = 0; }, child: Container( width: 60.px, padding: EdgeInsets.only(left: 6.px, right: 6.px), child: Text('商品', style: TextStyle(color: textColor) ,textAlign: TextAlign.center), ), ), InkWell( onTap: () { this.tabIdx = 2; }, child: Container( width: 60.px, padding: EdgeInsets.only(left: 6.px, right: 6.px), child: Text('评价', style: TextStyle(color: textColor) ,textAlign: TextAlign.center), ), ), InkWell( onTap: () { this.tabIdx = 1; }, child: Container( width: 60.px, padding: EdgeInsets.only(left: 6.px, right: 6.px), child: Text('详情', style: TextStyle(color: textColor) ,textAlign: TextAlign.center), ), ), ], ), ); } } class ProductContent extends StatelessWidget { final Details _details; ProductContent(this._details, {Key key}): super(key: key); _doSelectAttr (BuildContext context) { showModalBottomSheet( context: context, shape: RoundedRectangleBorder(borderRadius: BorderRadius.only( topLeft: Radius.circular(10.px), topRight: Radius.circular(10.px) )), builder: (BuildContext context) { return ProductAttrPage(_details, showAll: true); } ); } Widget _buildProductAttr(BuildContext context) { var attr = this._details?.productAttr; if(attr != null && attr.length > 0) { var attrs = ""; for(var item in attr) { attrs = "$attrs ${item.attr_name}"; } return Container( color: Colors.white, margin: EdgeInsets.only(top: 6.px), padding: EdgeInsets.only(left: 16.px, right: 10.px, top: 8.px, bottom: 8.px), child: InkWell( onTap: () => _doSelectAttr(context), child: Flex( direction: Axis.horizontal, children: [ RichText( text: TextSpan( text: "选择:", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w300, fontSize: 12.px), children: [ TextSpan( text: "请选择$attrs",// attr[0].attr_value[0].attr, style: TextStyle(color: Colors.black, fontSize: 12.px), ), ] ), ), Spacer(), Icon(Icons.keyboard_arrow_right, color: Colors.grey) ], ) ), ); } return Container(); } Widget _buildComment (BuildContext context) { return Container( color: Colors.white, margin: EdgeInsets.only(top: 6.px, bottom: 10.px), padding: EdgeInsets.only(left: 16.px, top: 10.px, right: 10.px, bottom: 10.px), child: InkWell( onTap: () { Navigator.pushNamed(context, RouteNames.comment, arguments: _details.uid.toString()); }, child: Row( children: [ Text('用户评价(${_details.replyCount})'), Spacer(), Text('${_details.replyChance}% ', style: TextStyle(color: Colors.red)), Text('好评率'), Icon(Icons.keyboard_arrow_right, color: Colors.grey) ], ), ), ); } @override Widget build(BuildContext context) { return Container( color: DColors.back, child: Column( children: [ Container( color: Colors.white, padding: EdgeInsets.only(left: 16.px, top: 16.px), child: Flex( direction: Axis.horizontal, children: [ RichText( text: TextSpan( text: I18n.$, style: TextStyle(fontSize: 12.px, color: Colors.red, fontWeight: FontWeight.bold), children: [ TextSpan( text: Utils.formatRMB(Cache.isVip ? _details.storeInfo.vip_price : _details.storeInfo.price), style: TextStyle(fontSize: 20.px, color: DColors.price, fontWeight: FontWeight.bold) ) ] ), ), Spacer() ], ) ), Container( color: Colors.white, width: double.infinity, padding: EdgeInsets.only(left: 16.px, top: 10.px, right: 16.px), child: Text(_details.storeInfo.store_name, style: TextStyle(height: 1.3.px, fontWeight: FontWeight.bold)) ), Container( color: Colors.white, padding: EdgeInsets.only(left: 16.px, top: 10.px, right: 16.px, bottom: 10.px), child: Flex( direction: Axis.horizontal, children: [ Text('原价:${Utils.formatRMB(_details?.storeInfo?.ot_price, show: true)}', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w300, fontSize: 12)), Spacer(), Text('库存:${_details?.storeInfo?.stock}', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w300, fontSize: 12)), Spacer(), Text('销量:${_details?.storeInfo?.fsales}', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w300, fontSize: 12)) ], ), ), _buildProductAttr(context), _buildComment(context), Container( color: Colors.white, padding: EdgeInsets.only(left: 12.px, top: 10.px, right: 12.px), child: _details?.storeInfo?.description == null ? Container() : Html(data: _details.storeInfo.description), ), ], ), ); } }