您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
410 行
17 KiB
410 行
17 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using com.unity.uiwidgets.Runtime.rendering;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.material;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
using Constants = Unity.UIWidgets.material.Constants;
|
|
using Image = Unity.UIWidgets.widgets.Image;
|
|
|
|
namespace UIWidgetsGallery.gallery {
|
|
class ShrineHomeUtils {
|
|
public const float unitSize = Constants.kToolbarHeight;
|
|
|
|
public static readonly List<Product> _products = new List<Product>(ShrineData.allProducts());
|
|
public static readonly Dictionary<Product, Order> _shoppingCart = new Dictionary<Product, Order>();
|
|
|
|
public const int _childrenPerBlock = 8;
|
|
public const int _rowsPerBlock = 5;
|
|
|
|
public static int _minIndexInRow(int rowIndex) {
|
|
int blockIndex = rowIndex / _rowsPerBlock;
|
|
return new List<int> {0, 2, 4, 6, 7}[rowIndex % _rowsPerBlock] + blockIndex * _childrenPerBlock;
|
|
}
|
|
|
|
public static int _maxIndexInRow(int rowIndex) {
|
|
int blockIndex = rowIndex / _rowsPerBlock;
|
|
return new List<int> {1, 3, 5, 6, 7}[rowIndex % _rowsPerBlock] + blockIndex * _childrenPerBlock;
|
|
}
|
|
|
|
public static int _rowAtIndex(int index) {
|
|
int blockCount = index / _childrenPerBlock;
|
|
return new List<int> {0, 0, 1, 1, 2, 2, 3, 4}[index - blockCount * _childrenPerBlock] +
|
|
blockCount * _rowsPerBlock;
|
|
}
|
|
|
|
public static int _columnAtIndex(int index) {
|
|
return new List<int> {0, 1, 0, 1, 0, 1, 0, 0}[index % _childrenPerBlock];
|
|
}
|
|
|
|
public static int _columnSpanAtIndex(int index) {
|
|
return new List<int> {1, 1, 1, 1, 1, 1, 2, 2}[index % _childrenPerBlock];
|
|
}
|
|
}
|
|
|
|
class _ShrineGridLayout : SliverGridLayout {
|
|
public _ShrineGridLayout(
|
|
float? rowStride = null,
|
|
float? columnStride = null,
|
|
float? tileHeight = null,
|
|
float? tileWidth = null
|
|
) {
|
|
this.rowStride = rowStride;
|
|
this.columnStride = columnStride;
|
|
this.tileHeight = tileHeight;
|
|
this.tileWidth = tileWidth;
|
|
}
|
|
|
|
public readonly float? rowStride;
|
|
public readonly float? columnStride;
|
|
public readonly float? tileHeight;
|
|
public readonly float? tileWidth;
|
|
|
|
public override int getMinChildIndexForScrollOffset(float scrollOffset) {
|
|
return ShrineHomeUtils._minIndexInRow((int) (scrollOffset / this.rowStride));
|
|
}
|
|
|
|
public override int getMaxChildIndexForScrollOffset(float scrollOffset) {
|
|
return ShrineHomeUtils._maxIndexInRow((int) (scrollOffset / this.rowStride));
|
|
}
|
|
|
|
public override SliverGridGeometry getGeometryForChildIndex(int index) {
|
|
int row = ShrineHomeUtils._rowAtIndex(index);
|
|
int column = ShrineHomeUtils._columnAtIndex(index);
|
|
int columnSpan = ShrineHomeUtils._columnSpanAtIndex(index);
|
|
return new SliverGridGeometry(
|
|
scrollOffset: row * this.rowStride,
|
|
crossAxisOffset: column * this.columnStride,
|
|
mainAxisExtent: this.tileHeight,
|
|
crossAxisExtent: this.tileWidth + (columnSpan - 1) * this.columnStride
|
|
);
|
|
}
|
|
|
|
public override float computeMaxScrollOffset(int childCount) {
|
|
if (childCount == 0) {
|
|
return 0.0f;
|
|
}
|
|
|
|
int rowCount = ShrineHomeUtils._rowAtIndex(childCount - 1) + 1;
|
|
float? rowSpacing = this.rowStride - this.tileHeight;
|
|
return (this.rowStride * rowCount - rowSpacing) ?? 0.0f;
|
|
}
|
|
}
|
|
|
|
class _ShrineGridDelegate : SliverGridDelegate {
|
|
const float _spacing = 8.0f;
|
|
|
|
public override SliverGridLayout getLayout(SliverConstraints constraints) {
|
|
float tileWidth = (constraints.crossAxisExtent - _spacing) / 2.0f;
|
|
const float tileHeight = 40.0f + 144.0f + 40.0f;
|
|
return new _ShrineGridLayout(
|
|
tileWidth: tileWidth,
|
|
tileHeight: tileHeight,
|
|
rowStride: tileHeight + _spacing,
|
|
columnStride: tileWidth + _spacing
|
|
);
|
|
}
|
|
|
|
public override bool shouldRelayout(SliverGridDelegate oldDelegate) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class _VendorItem : StatelessWidget {
|
|
public _VendorItem(Key key = null, Vendor vendor = null) : base(key: key) {
|
|
D.assert(vendor != null);
|
|
this.vendor = vendor;
|
|
}
|
|
|
|
public readonly Vendor vendor;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
return new SizedBox(
|
|
height: 24.0f,
|
|
child: new Row(
|
|
children: new List<Widget> {
|
|
new SizedBox(
|
|
width: 24.0f,
|
|
child: new ClipRRect(
|
|
borderRadius: BorderRadius.circular(12.0f),
|
|
child: Image.asset(
|
|
this.vendor.avatarAsset,
|
|
fit: BoxFit.cover
|
|
)
|
|
)
|
|
),
|
|
new SizedBox(width: 8.0f),
|
|
new Expanded(
|
|
child: new Text(this.vendor.name, style: ShrineTheme.of(context).vendorItemStyle)
|
|
)
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
abstract class _PriceItem : StatelessWidget {
|
|
public _PriceItem(Key key = null, Product product = null)
|
|
: base(key: key) {
|
|
D.assert(product != null);
|
|
this.product = product;
|
|
}
|
|
|
|
public readonly Product product;
|
|
|
|
public Widget buildItem(BuildContext context, TextStyle style, EdgeInsets padding) {
|
|
BoxDecoration decoration = null;
|
|
if (ShrineHomeUtils._shoppingCart.getOrDefault(this.product) != null) {
|
|
decoration = new BoxDecoration(color: ShrineTheme.of(context).priceHighlightColor);
|
|
}
|
|
|
|
return new Container(
|
|
padding: padding,
|
|
decoration: decoration,
|
|
child: new Text(this.product.priceString, style: style)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ProductPriceItem : _PriceItem {
|
|
public _ProductPriceItem(Key key = null, Product product = null) : base(key: key, product: product) {
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
return this.buildItem(
|
|
context,
|
|
ShrineTheme.of(context).priceStyle,
|
|
EdgeInsets.symmetric(horizontal: 16.0f, vertical: 8.0f)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _FeaturePriceItem : _PriceItem {
|
|
public _FeaturePriceItem(Key key = null, Product product = null) : base(key: key, product: product) {
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
return this.buildItem(
|
|
context,
|
|
ShrineTheme.of(context).featurePriceStyle,
|
|
EdgeInsets.symmetric(horizontal: 24.0f, vertical: 16.0f)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HeadingLayout : MultiChildLayoutDelegate {
|
|
public _HeadingLayout() {
|
|
}
|
|
|
|
public const string price = "price";
|
|
public const string image = "image";
|
|
public const string title = "title";
|
|
public const string description = "description";
|
|
public const string vendor = "vendor";
|
|
|
|
public override void performLayout(Size size) {
|
|
Size priceSize = this.layoutChild(price, BoxConstraints.loose(size));
|
|
this.positionChild(price, new Offset(size.width - priceSize.width, 0.0f));
|
|
|
|
float halfWidth = size.width / 2.0f;
|
|
float halfHeight = size.height / 2.0f;
|
|
const float halfUnit = ShrineHomeUtils.unitSize / 2.0f;
|
|
const float margin = 16.0f;
|
|
|
|
Size imageSize = this.layoutChild(image, BoxConstraints.loose(size));
|
|
float imageX = imageSize.width < halfWidth - halfUnit
|
|
? halfWidth / 2.0f - imageSize.width / 2.0f - halfUnit
|
|
: halfWidth - imageSize.width;
|
|
this.positionChild(image, new Offset(imageX, halfHeight - imageSize.height / 2.0f));
|
|
|
|
float maxTitleWidth = halfWidth + ShrineHomeUtils.unitSize - margin;
|
|
BoxConstraints titleBoxConstraints = new BoxConstraints(maxWidth: maxTitleWidth);
|
|
Size titleSize = this.layoutChild(title, titleBoxConstraints);
|
|
float titleX = halfWidth - ShrineHomeUtils.unitSize;
|
|
float titleY = halfHeight - titleSize.height;
|
|
this.positionChild(title, new Offset(titleX, titleY));
|
|
|
|
Size descriptionSize = this.layoutChild(description, titleBoxConstraints);
|
|
float descriptionY = titleY + titleSize.height + margin;
|
|
this.positionChild(description, new Offset(titleX, descriptionY));
|
|
|
|
this.layoutChild(vendor, titleBoxConstraints);
|
|
float vendorY = descriptionY + descriptionSize.height + margin;
|
|
this.positionChild(vendor, new Offset(titleX, vendorY));
|
|
}
|
|
|
|
public override bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class _HeadingShrineHome : StatelessWidget {
|
|
public _HeadingShrineHome(Key key = null, Product product = null) : base(key: key) {
|
|
D.assert(product != null);
|
|
D.assert(product.featureTitle != null);
|
|
D.assert(product.featureDescription != null);
|
|
this.product = product;
|
|
}
|
|
|
|
public readonly Product product;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Size screenSize = MediaQuery.of(context).size;
|
|
ShrineTheme theme = ShrineTheme.of(context);
|
|
return new SizedBox(
|
|
height: screenSize.width > screenSize.height
|
|
? (screenSize.height - Constants.kToolbarHeight) * 0.85f
|
|
: (screenSize.height - Constants.kToolbarHeight) * 0.70f,
|
|
child: new Container(
|
|
decoration: new BoxDecoration(
|
|
color: theme.cardBackgroundColor,
|
|
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
|
),
|
|
child: new CustomMultiChildLayout(
|
|
layoutDelegate: new _HeadingLayout(),
|
|
children: new List<Widget> {
|
|
new LayoutId(
|
|
id: _HeadingLayout.price,
|
|
child: new _FeaturePriceItem(product: this.product)
|
|
),
|
|
new LayoutId(
|
|
id: _HeadingLayout.image,
|
|
child: Image.asset(
|
|
this.product.imageAsset,
|
|
fit: BoxFit.cover
|
|
)
|
|
),
|
|
new LayoutId(
|
|
id: _HeadingLayout.title,
|
|
child: new Text(this.product.featureTitle, style: theme.featureTitleStyle)
|
|
),
|
|
new LayoutId(
|
|
id: _HeadingLayout.description,
|
|
child: new Text(this.product.featureDescription, style: theme.featureStyle)
|
|
),
|
|
new LayoutId(
|
|
id: _HeadingLayout.vendor,
|
|
child: new _VendorItem(vendor: this.product.vendor)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ProductItem : StatelessWidget {
|
|
public _ProductItem(Key key = null, Product product = null, VoidCallback onPressed = null) : base(key: key) {
|
|
D.assert(product != null);
|
|
this.product = product;
|
|
this.onPressed = onPressed;
|
|
}
|
|
|
|
public readonly Product product;
|
|
public readonly VoidCallback onPressed;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
return new Card(
|
|
child: new Stack(
|
|
children: new List<Widget> {
|
|
new Column(
|
|
children: new List<Widget> {
|
|
new Align(
|
|
alignment: Alignment.centerRight,
|
|
child: new _ProductPriceItem(product: this.product)
|
|
),
|
|
new Container(
|
|
width: 144.0f,
|
|
height: 144.0f,
|
|
padding: EdgeInsets.symmetric(horizontal: 8.0f),
|
|
child:new Hero(
|
|
tag: this.product.tag,
|
|
child: Image.asset(this.product.imageAsset,
|
|
fit: BoxFit.contain
|
|
)
|
|
)
|
|
),
|
|
new Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.0f),
|
|
child: new _VendorItem(vendor: this.product.vendor)
|
|
)
|
|
}
|
|
),
|
|
new Material(
|
|
type: MaterialType.transparency,
|
|
child: new InkWell(onTap: this.onPressed == null
|
|
? (GestureTapCallback) null
|
|
: () => { this.onPressed(); })
|
|
)
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public class ShrineHome : StatefulWidget {
|
|
public override State createState() {
|
|
return new _ShrineHomeState();
|
|
}
|
|
}
|
|
|
|
class _ShrineHomeState : State<ShrineHome> {
|
|
public _ShrineHomeState() {
|
|
}
|
|
|
|
readonly GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>.key(debugLabel: "Shrine Home");
|
|
static readonly _ShrineGridDelegate gridDelegate = new _ShrineGridDelegate();
|
|
|
|
void _showOrderPage(Product product) {
|
|
Order order = ShrineHomeUtils._shoppingCart.getOrDefault(product) ?? new Order(product: product);
|
|
Navigator.push(this.context, new ShrineOrderRoute(
|
|
order: order,
|
|
builder: (BuildContext context) => {
|
|
return new OrderPage(
|
|
order: order,
|
|
products: ShrineHomeUtils._products,
|
|
shoppingCart: ShrineHomeUtils._shoppingCart
|
|
);
|
|
}
|
|
)).Then(completedOrder => {
|
|
D.assert((completedOrder as Order).product != null);
|
|
if ((completedOrder as Order).quantity == 0) {
|
|
ShrineHomeUtils._shoppingCart.Remove((completedOrder as Order).product);
|
|
}
|
|
});
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Product featured = ShrineHomeUtils._products.First((Product product) => product.featureDescription != null);
|
|
return new ShrinePage(
|
|
scaffoldKey: this._scaffoldKey,
|
|
products: ShrineHomeUtils._products,
|
|
shoppingCart: ShrineHomeUtils._shoppingCart,
|
|
body: new CustomScrollView(
|
|
slivers: new List<Widget> {
|
|
new SliverToBoxAdapter(child: new _HeadingShrineHome(product: featured)),
|
|
new SliverSafeArea(
|
|
top: false,
|
|
minimum: EdgeInsets.all(16.0f),
|
|
sliver: new SliverGrid(
|
|
gridDelegate: gridDelegate,
|
|
layoutDelegate: new SliverChildListDelegate(
|
|
ShrineHomeUtils._products.Select<Product, Widget>((Product product) => {
|
|
return new _ProductItem(
|
|
product: product,
|
|
onPressed: () => { this._showOrderPage(product); }
|
|
);
|
|
}).ToList()
|
|
)
|
|
)
|
|
)
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|