承接上篇文章 D-20 Provider 購物車實作(上) | Flutter筆記
介紹購物車設計
購物車CartModel實體,存在於多個畫面
否則目錄頁加一件商品,卻不會在購物車裡面
import 'dart:collection';
import 'package:flutter/widgets.dart';
import 'package:flutter_account_note/shop/model/catalog.dart';
class CartModel extends ChangeNotifier {
  final List<int> _itemIds = []; //加購的商品id存放
  late CatalogModel _catalog; //私有目錄 僅能透過以下get,set 讀取和寫入
  //1. 取得目錄內容
  CatalogModel get catalog => _catalog;
  //2. 設定目錄內容
  set catalog(CatalogModel newCatalog) {
    _catalog = newCatalog;
    notifyListeners();
  }
  //---------------------
  //_itemIds是選購的商品
  //map將id快速轉換成List<item>
  //白話: 購物車商品列表
  List<Item> get items => _itemIds.map((id) => _catalog.getById(id)).toList();
  //總價格計算 fold會將list內容加總 , 0代表初始值
  int get totalPrice => items.fold(0, (total, current) => total + current.price);
  //------------------
  // add removeAll
  // 要對購物車操作 新增商品 移除全部
  // 所以皆需要notifyListeners 告訴我們畫面要更新了
  void add(Item item) {
    _itemIds.add(item.id);
    notifyListeners();
  }
  void removeAll() {
    _itemIds.clear();
    notifyListeners();
  }
  void remove(Item item) {
    _itemIds.remove(item.id);
    notifyListeners();
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_account_note/shop/model/cart.dart';
import 'package:flutter_account_note/shop/model/catalog.dart';
import 'package:provider/provider.dart';
class Catalog extends StatelessWidget {
  const Catalog({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          _appBar(),
          const SliverToBoxAdapter(
            child: SizedBox(height: 12),
          ),
          //產生商品列表 單一個商品由_listItem產生
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => _listItem(index),
              childCount: 4,
            ),
          ),
        ],
      ),
    );
  }
}
class _appBar extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      title: Text(
        'Catalog',
        style: Theme.of(context).textTheme.displayMedium,
      ),
      actions: [
        IconButton(
          onPressed: () => Navigator.pushNamed(context, '/cart'),
          icon: const Icon(Icons.shopping_cart),
        )
      ],
    );
  }
}
class _listItem extends StatelessWidget {
  final int index;
  const _listItem(this.index);
  @override
  Widget build(BuildContext context) {
    //讀取CatalogModel中的商品目錄index 代表得到目錄中第幾個
    var item = context.select<CatalogModel, Item>(
      (catalog) => catalog.getByPosition(index),
    );
    var textTheme = Theme.of(context).textTheme.titleLarge;
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: LimitedBox(
        maxHeight: 48,
        child: Row(
          children: [
            AspectRatio(
              aspectRatio: 1,
              child: Container(
                color: item.color,
              ),
            ),
            const SizedBox(width: 24),
            Expanded(
              child: Text(item.name, style: textTheme),
            ),
            const SizedBox(width: 24),
            _addButton(item: item),
          ],
        ),
      ),
    );
  }
}
//商品按鈕 點擊後加入購物車
class _addButton extends StatelessWidget {
  const _addButton({Key? key, required this.item}) : super(key: key);
  final Item item;
  @override
  Widget build(BuildContext context) {
  
    //檢查商品(Item)是否在購物車(Cart)中
    var isInCart = context.select<CartModel, bool>((cart) => cart.items.contains(item));
    return TextButton(
      onPressed: isInCart
          ? null
          : () {
              //點擊後 , 條件判斷沒有在購物車的話 ,即加入購物車
              var cart = context.read<CartModel>();
              cart.add(item);
            },
      child: isInCart ? const Icon(Icons.check, semanticLabel: 'ADDED') : const Text('ADD'),
      style: ButtonStyle(
        overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
          if (states.contains(MaterialState.pressed)) {
            return Theme.of(context).primaryColor;
          }
          return null;
        }),
      ),
    );
  }
}