mirror of
https://github.com/flutter/samples.git
synced 2025-11-11 23:39:14 +00:00
346 lines
12 KiB
Dart
346 lines
12 KiB
Dart
// Copyright 2019 The Flutter team. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:scoped_model/scoped_model.dart';
|
|
|
|
import 'package:gallery/studies/shrine/colors.dart';
|
|
import 'package:gallery/studies/shrine/expanding_bottom_sheet.dart';
|
|
import 'package:gallery/studies/shrine/model/app_state_model.dart';
|
|
import 'package:gallery/studies/shrine/model/product.dart';
|
|
import 'package:gallery/studies/shrine/theme.dart';
|
|
import 'package:gallery/l10n/gallery_localizations.dart';
|
|
|
|
const _startColumnWidth = 60.0;
|
|
const _ordinalSortKeyName = 'shopping_cart';
|
|
|
|
class ShoppingCartPage extends StatefulWidget {
|
|
@override
|
|
_ShoppingCartPageState createState() => _ShoppingCartPageState();
|
|
}
|
|
|
|
class _ShoppingCartPageState extends State<ShoppingCartPage> {
|
|
List<Widget> _createShoppingCartRows(AppStateModel model) {
|
|
return model.productsInCart.keys
|
|
.map(
|
|
(id) => ShoppingCartRow(
|
|
product: model.getProductById(id),
|
|
quantity: model.productsInCart[id],
|
|
onPressed: () {
|
|
model.removeItemFromCart(id);
|
|
},
|
|
),
|
|
)
|
|
.toList();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData localTheme = Theme.of(context);
|
|
return Scaffold(
|
|
backgroundColor: shrinePink50,
|
|
body: SafeArea(
|
|
child: Container(
|
|
child: ScopedModelDescendant<AppStateModel>(
|
|
builder: (context, child, model) {
|
|
return Stack(
|
|
children: [
|
|
ListView(
|
|
children: [
|
|
Semantics(
|
|
sortKey: OrdinalSortKey(0, name: _ordinalSortKeyName),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: _startColumnWidth,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.keyboard_arrow_down),
|
|
onPressed: () =>
|
|
ExpandingBottomSheet.of(context).close(),
|
|
tooltip: GalleryLocalizations.of(context)
|
|
.shrineTooltipCloseCart,
|
|
),
|
|
),
|
|
Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineCartPageCaption,
|
|
style: localTheme.textTheme.subhead
|
|
.copyWith(fontWeight: FontWeight.w600),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineCartItemCount(
|
|
model.totalCartQuantity,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Semantics(
|
|
sortKey: OrdinalSortKey(1, name: _ordinalSortKeyName),
|
|
child: Column(
|
|
children: _createShoppingCartRows(model),
|
|
),
|
|
),
|
|
Semantics(
|
|
sortKey: OrdinalSortKey(2, name: _ordinalSortKeyName),
|
|
child: ShoppingCartSummary(model: model),
|
|
),
|
|
const SizedBox(height: 100),
|
|
],
|
|
),
|
|
PositionedDirectional(
|
|
bottom: 16,
|
|
start: 16,
|
|
end: 16,
|
|
child: Semantics(
|
|
sortKey: OrdinalSortKey(3, name: _ordinalSortKeyName),
|
|
child: RaisedButton(
|
|
shape: const BeveledRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(7)),
|
|
),
|
|
color: shrinePink100,
|
|
splashColor: shrineBrown600,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 12),
|
|
child: Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineCartClearButtonCaption,
|
|
style: TextStyle(letterSpacing: largeLetterSpacing),
|
|
),
|
|
),
|
|
onPressed: () {
|
|
model.clearCart();
|
|
ExpandingBottomSheet.of(context).close();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ShoppingCartSummary extends StatelessWidget {
|
|
const ShoppingCartSummary({this.model});
|
|
|
|
final AppStateModel model;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final TextStyle smallAmountStyle =
|
|
Theme.of(context).textTheme.body1.copyWith(color: shrineBrown600);
|
|
final TextStyle largeAmountStyle = Theme.of(context)
|
|
.textTheme
|
|
.display1
|
|
.copyWith(letterSpacing: mediumLetterSpacing);
|
|
final NumberFormat formatter = NumberFormat.simpleCurrency(
|
|
decimalDigits: 2,
|
|
locale: Localizations.localeOf(context).toString(),
|
|
);
|
|
|
|
return Row(
|
|
children: [
|
|
const SizedBox(width: _startColumnWidth),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsetsDirectional.only(end: 16),
|
|
child: Column(
|
|
children: [
|
|
MergeSemantics(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
GalleryLocalizations.of(context).shrineCartTotalCaption,
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
formatter.format(model.totalCost),
|
|
style: largeAmountStyle,
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
MergeSemantics(
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineCartSubtotalCaption,
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
formatter.format(model.subtotalCost),
|
|
style: smallAmountStyle,
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
MergeSemantics(
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineCartShippingCaption,
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
formatter.format(model.shippingCost),
|
|
style: smallAmountStyle,
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
MergeSemantics(
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
GalleryLocalizations.of(context).shrineCartTaxCaption,
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
formatter.format(model.tax),
|
|
style: smallAmountStyle,
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class ShoppingCartRow extends StatelessWidget {
|
|
const ShoppingCartRow({
|
|
@required this.product,
|
|
@required this.quantity,
|
|
this.onPressed,
|
|
});
|
|
|
|
final Product product;
|
|
final int quantity;
|
|
final VoidCallback onPressed;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final NumberFormat formatter = NumberFormat.simpleCurrency(
|
|
decimalDigits: 0,
|
|
locale: Localizations.localeOf(context).toString(),
|
|
);
|
|
final ThemeData localTheme = Theme.of(context);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16),
|
|
child: Row(
|
|
key: ValueKey<int>(product.id),
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Semantics(
|
|
container: true,
|
|
label: GalleryLocalizations.of(context)
|
|
.shrineScreenReaderRemoveProductButton(product.name(context)),
|
|
button: true,
|
|
child: ExcludeSemantics(
|
|
child: SizedBox(
|
|
width: _startColumnWidth,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.remove_circle_outline),
|
|
onPressed: onPressed,
|
|
tooltip:
|
|
GalleryLocalizations.of(context).shrineTooltipRemoveItem,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsetsDirectional.only(end: 16),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Image.asset(
|
|
product.assetName,
|
|
package: product.assetPackage,
|
|
fit: BoxFit.cover,
|
|
width: 75,
|
|
height: 75,
|
|
excludeFromSemantics: true,
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: MergeSemantics(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
MergeSemantics(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineProductQuantity(quantity),
|
|
),
|
|
),
|
|
Text(
|
|
GalleryLocalizations.of(context)
|
|
.shrineProductPrice(
|
|
formatter.format(product.price),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
product.name(context),
|
|
style: localTheme.textTheme.subhead
|
|
.copyWith(fontWeight: FontWeight.w600),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Divider(
|
|
color: shrineBrown900,
|
|
height: 10,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|