講者花了好多時間在整理檔案架構,這次就有點像物件導向的整理方式吧!
1.檔案架構
models -
products.dart //定義了Product類別(標題,價格,圖片屬性等等)
user.dart //定義了User類別(email,密碼,id等等)
pages -
auth.dart //登入頁面
product_edit.dart //管理者新增與修改頁面
product_list.dart //管理者商品清單頁面
product.dart //使用者商品細節頁面
product_admin.dart //管理者切換edit和list頁籤的那頁
products.dart //使用者商品列表頁面
scoped-models -
connected-products.dart //把所有對products類別產生的實例的操作寫在這
main.dart //把User類別,Product類別以及,ConnectedProducts類別輸出到最外面的main.dart
widgets -
helpers -
ensure_visible.dart //讓input focus的時候看得到的工具
products -
address_tag.dart //標籤樣式的地址
price_tag.dart //標籤樣式的價格
product_card.dart //商品卡
products.dart //商品列表
ui-element -
title_default.dart //商品名稱的字型公版
main.dart
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
scoped_model: "^0.3.0"
另外新增了一個功能
將商品加入收藏的功能
並可以利用畫面右上角的愛心來切換全部清單/收藏清單
1.main.dart
void main() { runApp(MyApp()); }
class MyApp extends StatefulWidget {...} 呼叫createState並回傳_MyAppState
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: MainModel(),
child: MaterialApp(
theme: ThemeData(...),
routes: { ... }, //登入頁 /商品列表頁 /管理者頁
onGenerateRoute: (RouteSettings settings) {...}, //若網址有編號就到細節頁
onUnknownRoute: (RouteSettings settings) {...}, //若上面回傳null就到商品列表頁
),
);
}
}
class AuthPage extends StatefulWidget {...} 呼叫createState並回傳_AuthPageState
class _AuthPageState extends State<AuthPage> {
final Map<String, dynamic> _formData = {
'email': null,
'password': null,
'acceptTerms': false
};
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); //表單驗證用的
DecorationImage _buildBackgroundImage() {...} //登入頁的背景
Widget _buildEmailTextField() {...} //email的驗證與寫到_formData['email']
Widget _buildPasswordTextField() {...} //password的驗證與寫到_formData['password']
Widget _buildAcceptSwitch() {...} //接受條款寫到_formData['acceptTerms']
void _submitForm(Function login) {
... //acceptTerms是否為true
_formKey.currentState.save(); //用來呼叫驗證用的
login(_formData['email'], _formData['password']); //從connected_products.dart傳來的login
Navigator.pushReplacementNamed(context, '/products'); //去商品頁
}
@override
Widget build(BuildContext context) {
...設定螢幕寬與內距的關係
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Container(
decoration: BoxDecoration(
image: _buildBackgroundImage(),
),
padding: EdgeInsets.all(10.0),
child: Center(
child: SingleChildScrollView(
child: Container(
width: targetWidth,
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
_buildEmailTextField(), ...
_buildPasswordTextField(),
_buildAcceptSwitch(), ...
ScopedModelDescendant<MainModel>( //把自己寫的類別變資料形式了
builder: (BuildContext context, Widget child, MainModel model) {
return RaisedButton(
textColor: Colors.white,
child: Text('LOGIN'),
onPressed: () => _submitForm(model.login),
//使用類別傳進來的model物件的login方法
...
class MainModel extends Model with ConnectedProductsModel, UserModel, ProductsModel {
}
// with關鍵字可以把其他model一併輸出
4.scoped-models/connected_products.dart
class ConnectedProductsModel extends Model {
List<Product> _products = []; //自己寫的product變資料類型了
int _selProductIndex;
User _authenticatedUser; //自己寫的User變資料類型了
void addProduct(String title, String description, String image, double price) {
final Product newProduct = Product(
title: title,
description: description,
image: image,
price: price,
userEmail: _authenticatedUser.email,
userId: _authenticatedUser.id
);
_products.add(newProduct);
notifyListeners(); //動作作完後,去改變UI呈現
}
}
class ProductsModel extends ConnectedProductsModel {
bool _showFavorites = false;
List<Product> get allProducts {
return List.from(_products);
//get是為要讓allProducts和_products存在不同記憶體位置
//外面也可以call這個function
//商品列表相關檔案都會呼叫他(等於沒有在其他檔案宣告_products了)
}
List<Product> get displayedProducts {
if (_showFavorites) {
return _products.where((Product product) => product.isFavorite).toList();
}
return List.from(_products);
//若_showFavorites是true,就只顯示最喜歡的商品列表
//商品列表頁面有傳入他當參數
}
int get selectedProductIndex {
return _selProductIndex; //編輯商品的時候使用
}
Product get selectedProduct {
if (selectedProductIndex == null) { //直接使用上面的function
return null;
}
return _products[selectedProductIndex];
//回傳整個商品物件
//編輯商品時使用
}
bool get displayFavoritesOnly {
return _showFavorites;
//是否只顯示收藏商品的布林值
//商品頁面header右上角的愛心有用到
}
void updateProduct(String title, String description, String image, double price) {
final Product updatedProduct = Product(
title: title,
description: description,
image: image,
price: price,
userEmail: selectedProduct.userEmail,
userId: selectedProduct.userId
);
_products[selectedProductIndex] = updatedProduct;
notifyListeners(); //編輯商品資訊
}
void deleteProduct() {
_products.removeAt(selectedProductIndex);
notifyListeners(); //刪除商品
}
void toggleProductFavoriteStatus() {
final bool isCurrentlyFavorite = selectedProduct.isFavorite;
//只接使用上面的function抓到現在商品細節的商品內容
final bool newFavoriteStatus = !isCurrentlyFavorite;
//把喜歡改成不喜歡,不喜歡改成喜歡
final Product updatedProduct = Product(
title: selectedProduct.title,
description: selectedProduct.description,
price: selectedProduct.price,
image: selectedProduct.image,
userEmail: selectedProduct.userEmail,
userId: selectedProduct.userId,
isFavorite: newFavoriteStatus);
_products[selectedProductIndex] = updatedProduct;
notifyListeners();
//存進陣列
}
void selectProduct(int index) {
_selProductIndex = index;
notifyListeners();
//把目前選到的商品編號存入變數
}
void toggleDisplayMode() {
_showFavorites = !_showFavorites;
notifyListeners();
//切換是否只顯示喜歡的商品
}
}
class UserModel extends ConnectedProductsModel {
void login(String email, String password) {
_authenticatedUser = User(id: 'fdalsdfasf', email: email, password: password);
//把_authenticatedUser變數設成一個用User類別傳入登入時的email和密碼以及寫死的id所產生的User實例
}
}
class Product {
final String title;
final String description;
final double price;
final String image;
final bool isFavorite;
final String userEmail;
final String userId;
Product(
{@required this.title,
@required this.description,
@required this.price,
@required this.image,
@required this.userEmail,
@required this.userId,
this.isFavorite = false});
}
import 'package:flutter/material.dart';
class User {
final String id;
final String email;
final String password;
User({@required this.id, @required this.email, @required this.password});
}
Widget build(BuildContext context) {
return Scaffold(
drawer: _buildSideDrawer(context),
appBar: AppBar(
title: Text('EasyList'),
actions: <Widget>[
ScopedModelDescendant<MainModel>(
//因為要使用model的變數,故呼叫此類別
builder: (BuildContext context, Widget child, MainModel model) {
return IconButton(
icon: Icon(model.displayFavoritesOnly
//若connected_products的getter displayFavoritesOnly 是true
? Icons.favorite //就是實心愛心
: Icons.favorite_border), //不然是邊框愛心
onPressed: () {
model.toggleDisplayMode();
//呼叫若connected_products的toggleDisplayMode
...
8.widgets/products/products.dart
商品列表
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(builder: (BuildContext context, Widget child, MainModel model) {
return _buildProductList(model.displayedProducts);
//傳入connected_products的displayedProducts
//若傳入陣列長度大於零
//就用ListView.builder跑出商品列表
},);
}
Widget _buildActionButtons(BuildContext context) {
//愛心的部分
return ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(...), //切到細節頁面的按鈕
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return IconButton(
icon: Icon(model.allProducts[productIndex].isFavorite
? Icons.favorite
: Icons.favorite_border),
//用此商品編號的favorite來決定是否是實心愛心
color: Colors.red,
onPressed: () {
model.selectProduct(productIndex);
//改變model裡面的商品編號變數
model.toggleProductFavoriteStatus();
//讓這個function可以知道要去改哪個商品的被喜歡與否
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {...}, //若back按鈕被按下時,傳一個false回去
child: ScopedModelDescendant<MainModel>(
//要使用model了
builder: (BuildContext context, Widget child, MainModel model) {
final Product product = model.allProducts[productIndex];
//productIndex是從route name抓到的數字
return Scaffold(
appBar: AppBar(...),
body: Column(...) //把product物件裡面的資訊都放進去
pages/product_admin.dart
幾乎沒改動
pages/product_edit.dart
Widget _buildSubmitButton() {
return ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return RaisedButton(
child: Text('Save'),
textColor: Colors.white,
onPressed: () => _submitForm(model.addProduct, model.updateProduct,
model.selectProduct, model.selectedProductIndex),
//_submitForm下面會有定義
);
},
);
}
void _submitForm(Function addProduct, Function updateProduct, Function setSelectedProduct,[int selectedProductIndex]) {
//index有可能是null,因為新增的時候沒有
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
if (selectedProductIndex == null) {
addProduct(
_formData['title'],
_formData['description'],
_formData['image'],
_formData['price'],
);
} else {
updateProduct(
_formData['title'],
_formData['description'],
_formData['image'],
_formData['price'],
);
}
Navigator
.pushReplacementNamed(context, '/products') //回到商品列表頁面
.then((_) => setSelectedProduct(null)); //改變全域的編號,變成null
//沒有傳參數,所以畫一個底線
}
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
final Widget pageContent = _buildPageContent(context, model.selectedProduct); //新增狀態
return model.selectedProductIndex == null
? pageContent
: Scaffold(
appBar: AppBar(
title: Text('Edit Product'),
),
body: pageContent, //編輯狀態
);
},
);
}
}
class ProductListPage extends StatelessWidget {
Widget _buildEditButton(BuildContext context, int index, MainModel model) {
return IconButton(
icon: Icon(Icons.edit),
onPressed: () {
model.selectProduct(index);
//把要編輯的項次傳到全域
Navigator.of(context).push(
//直接產生一個最外面route沒定義的頁面
//也就是商品編輯頁
MaterialPageRoute(
builder: (BuildContext context) {
return ProductEditPage();
....
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
//要使用model了
builder: (BuildContext context, Widget child, MainModel model) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Dismissible(
key: Key(model.allProducts[index].title),
//拿title當key
onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.endToStart) {
model.selectProduct(index);
model.deleteProduct();
//改變全域的編號
//根據全域的編號刪掉商品
}
...
},
background: Container(color: Colors.red),
child: Column(
children: <Widget>[
ListTile(
... //商品列表的一些樣式
trailing: _buildEditButton(context, index, model),