feat: 1
This commit is contained in:
parent
27a92960c5
commit
a010670dad
|
@ -5,10 +5,10 @@ class GloablConfig {
|
||||||
// static const BASE_URL = "http://10.0.2.2:8001/api/";
|
// static const BASE_URL = "http://10.0.2.2:8001/api/";
|
||||||
// static const OSS_URL = "http://10.0.2.2:8001";
|
// static const OSS_URL = "http://10.0.2.2:8001";
|
||||||
|
|
||||||
// static const BASE_URL = "http://144.123.43.138:3001/api/";
|
static const BASE_URL = "http://144.123.43.138:3001/api/";
|
||||||
// static const OSS_URL = "http://144.123.43.138:3001";
|
static const OSS_URL = "http://144.123.43.138:3001";
|
||||||
static const BASE_URL = "http://192.168.60.220:8001/api/";
|
// static const BASE_URL = "http://192.168.60.220:8001/api/";
|
||||||
static const OSS_URL = "http://192.168.60.220:8001";
|
// static const OSS_URL = "http://192.168.60.220:8001";
|
||||||
static const DOMAIN_NAME = "山矿通";
|
static const DOMAIN_NAME = "山矿通";
|
||||||
static const DEBUG = true;
|
static const DEBUG = true;
|
||||||
static const PRIVACY_POLICY = 'http://h5.heeru.xyz/privacyPolicy.html';
|
static const PRIVACY_POLICY = 'http://h5.heeru.xyz/privacyPolicy.html';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart';
|
import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||||
|
|
||||||
class InventoryPage extends StatelessWidget {
|
class InventoryPage extends StatelessWidget {
|
||||||
const InventoryPage({super.key});
|
const InventoryPage({super.key});
|
||||||
|
@ -7,7 +8,7 @@ class InventoryPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('库存管理')),
|
appBar: const SkAppbar(title: '库存管理'),
|
||||||
body: InventorySearch(),
|
body: InventorySearch(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,19 @@ class LandingPage extends StatelessWidget {
|
||||||
return Material(
|
return Material(
|
||||||
child: Stack(children: [
|
child: Stack(children: [
|
||||||
const BackColors(),
|
const BackColors(),
|
||||||
Scaffold(
|
Obx(() => Scaffold(
|
||||||
floatingActionButtonLocation:
|
floatingActionButtonLocation:
|
||||||
FloatingActionButtonLocation.centerDocked,
|
FloatingActionButtonLocation.centerDocked,
|
||||||
// floatingActionButton: [0].indexWhere(
|
floatingActionButton: buildBody(),
|
||||||
// (item) => item == controller.currentIndex.value) >
|
bottomNavigationBar: BottomNavBar(),
|
||||||
// -1
|
backgroundColor: Colors.transparent,
|
||||||
// ? FloatingCreateButton()
|
body: Obx(() => controller.pages[controller.currentIndex.value]),
|
||||||
// : null,
|
))
|
||||||
floatingActionButton: FloatingCreateButton(),
|
|
||||||
bottomNavigationBar: BottomNavBar(),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: Obx(() => controller.pages[controller.currentIndex.value]),
|
|
||||||
)
|
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget? buildBody() {
|
||||||
|
return controller.currentIndex.value == 0 ? FloatingCreateButton() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,178 +192,180 @@ class InventorySearch extends StatelessWidget {
|
||||||
// 库存列表
|
// 库存列表
|
||||||
Widget buildInventoryList() {
|
Widget buildInventoryList() {
|
||||||
final textStyle = TextStyle(fontSize: ScreenAdaper.height(25));
|
final textStyle = TextStyle(fontSize: ScreenAdaper.height(25));
|
||||||
return Obx(() => SmartRefresher(
|
return Obx(() => SafeArea(
|
||||||
enablePullDown: true,
|
child: SmartRefresher(
|
||||||
enablePullUp: true,
|
enablePullDown: true,
|
||||||
controller: controller.refreshController,
|
enablePullUp: true,
|
||||||
onLoading: controller.onLoading,
|
controller: controller.refreshController,
|
||||||
onRefresh: controller.onRefresh,
|
onLoading: controller.onLoading,
|
||||||
child: controller.refreshController.isLoading
|
onRefresh: controller.onRefresh,
|
||||||
? const SizedBox()
|
child: controller.refreshController.isLoading
|
||||||
: controller.inventories.isEmpty
|
? const SizedBox()
|
||||||
? const Center(
|
: controller.inventories.isEmpty
|
||||||
child: Empty(text: '暂无库存'),
|
? const Center(
|
||||||
)
|
child: Empty(text: '暂无库存'),
|
||||||
: !ScreenAdaper.isLandspace()
|
)
|
||||||
? buildPortraitList()
|
: !ScreenAdaper.isLandspace()
|
||||||
: Table(columnWidths: {
|
? buildPortraitList()
|
||||||
0: const MinColumnWidth(
|
: Table(columnWidths: {
|
||||||
FixedColumnWidth(80), FixedColumnWidth(80)),
|
0: const MinColumnWidth(
|
||||||
1: MinColumnWidth(
|
FixedColumnWidth(80), FixedColumnWidth(80)),
|
||||||
FixedColumnWidth(
|
1: MinColumnWidth(
|
||||||
ScreenAdaper.screenShortDistance() / 5),
|
FixedColumnWidth(
|
||||||
FixedColumnWidth(
|
ScreenAdaper.screenShortDistance() / 5),
|
||||||
ScreenAdaper.screenShortDistance() / 5)),
|
FixedColumnWidth(
|
||||||
2: FlexColumnWidth(
|
ScreenAdaper.screenShortDistance() / 5)),
|
||||||
ScreenAdaper.screenShortDistance() / 4),
|
2: FlexColumnWidth(
|
||||||
3: FlexColumnWidth(
|
ScreenAdaper.screenShortDistance() / 4),
|
||||||
ScreenAdaper.screenShortDistance() / 4),
|
3: FlexColumnWidth(
|
||||||
4: MinColumnWidth(
|
ScreenAdaper.screenShortDistance() / 4),
|
||||||
FixedColumnWidth(
|
4: MinColumnWidth(
|
||||||
ScreenAdaper.screenShortDistance() / 5),
|
FixedColumnWidth(
|
||||||
FixedColumnWidth(
|
ScreenAdaper.screenShortDistance() / 5),
|
||||||
ScreenAdaper.screenShortDistance() / 5)),
|
FixedColumnWidth(
|
||||||
5: MinColumnWidth(
|
ScreenAdaper.screenShortDistance() / 5)),
|
||||||
FixedColumnWidth(
|
5: MinColumnWidth(
|
||||||
ScreenAdaper.screenShortDistance() / 6),
|
FixedColumnWidth(
|
||||||
FixedColumnWidth(
|
ScreenAdaper.screenShortDistance() / 6),
|
||||||
ScreenAdaper.screenShortDistance() / 6)),
|
FixedColumnWidth(
|
||||||
}, children: [
|
ScreenAdaper.screenShortDistance() / 6)),
|
||||||
// table header
|
}, children: [
|
||||||
TableRow(
|
// table header
|
||||||
decoration: const BoxDecoration(
|
TableRow(
|
||||||
border: Border(
|
decoration: const BoxDecoration(
|
||||||
bottom: BorderSide(
|
border: Border(
|
||||||
color: AppTheme.dividerColor))),
|
bottom: BorderSide(
|
||||||
children: [
|
color: AppTheme.dividerColor))),
|
||||||
TableCell(
|
children: [
|
||||||
verticalAlignment:
|
TableCell(
|
||||||
TableCellVerticalAlignment.middle,
|
verticalAlignment:
|
||||||
child: Container(
|
TableCellVerticalAlignment.middle,
|
||||||
alignment: Alignment.centerLeft,
|
child: Container(
|
||||||
height: ScreenAdaper.height(60),
|
alignment: Alignment.centerLeft,
|
||||||
|
height: ScreenAdaper.height(60),
|
||||||
|
child: Text(
|
||||||
|
'库存编号',
|
||||||
|
style: listTitleTextStyle,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
verticalAlignment:
|
||||||
|
TableCellVerticalAlignment.middle,
|
||||||
|
child: Text(
|
||||||
|
'所属项目',
|
||||||
|
style: listTitleTextStyle,
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
verticalAlignment:
|
||||||
|
TableCellVerticalAlignment.middle,
|
||||||
|
child: Text(
|
||||||
|
'产品名称',
|
||||||
|
style: listTitleTextStyle,
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
verticalAlignment:
|
||||||
|
TableCellVerticalAlignment.middle,
|
||||||
child: Text(
|
child: Text(
|
||||||
'库存编号',
|
'规格',
|
||||||
style: listTitleTextStyle,
|
style: listTitleTextStyle,
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
TableCell(
|
TableCell(
|
||||||
verticalAlignment:
|
verticalAlignment:
|
||||||
TableCellVerticalAlignment.middle,
|
TableCellVerticalAlignment.middle,
|
||||||
child: Text(
|
child: Text(
|
||||||
'所属项目',
|
'单价',
|
||||||
style: listTitleTextStyle,
|
style: listTitleTextStyle,
|
||||||
)),
|
|
||||||
TableCell(
|
|
||||||
verticalAlignment:
|
|
||||||
TableCellVerticalAlignment.middle,
|
|
||||||
child: Text(
|
|
||||||
'产品名称',
|
|
||||||
style: listTitleTextStyle,
|
|
||||||
)),
|
|
||||||
TableCell(
|
|
||||||
verticalAlignment:
|
|
||||||
TableCellVerticalAlignment.middle,
|
|
||||||
child: Text(
|
|
||||||
'规格',
|
|
||||||
style: listTitleTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
verticalAlignment:
|
|
||||||
TableCellVerticalAlignment.middle,
|
|
||||||
child: Text(
|
|
||||||
'单价',
|
|
||||||
style: listTitleTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TableCell(
|
|
||||||
verticalAlignment:
|
|
||||||
TableCellVerticalAlignment.middle,
|
|
||||||
child: Text(
|
|
||||||
'数量',
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: listTitleTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
...controller.inventories.map((itemData) {
|
|
||||||
return TableRow(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: AppTheme.dividerColor))),
|
|
||||||
children: [
|
|
||||||
buildTableCell(
|
|
||||||
Text(
|
|
||||||
itemData.inventoryNumber!,
|
|
||||||
style: textStyle,
|
|
||||||
),
|
),
|
||||||
itemData: itemData),
|
),
|
||||||
|
TableCell(
|
||||||
// 入库时所属项目
|
verticalAlignment:
|
||||||
buildTableCell(
|
TableCellVerticalAlignment.middle,
|
||||||
Column(
|
child: Text(
|
||||||
mainAxisAlignment:
|
'数量',
|
||||||
MainAxisAlignment.center,
|
textAlign: TextAlign.right,
|
||||||
crossAxisAlignment:
|
style: listTitleTextStyle,
|
||||||
CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
),
|
||||||
|
]),
|
||||||
|
...controller.inventories.map((itemData) {
|
||||||
|
return TableRow(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: AppTheme.dividerColor))),
|
||||||
|
children: [
|
||||||
|
buildTableCell(
|
||||||
Text(
|
Text(
|
||||||
'${itemData.project?.name}',
|
itemData.inventoryNumber!,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
],
|
itemData: itemData),
|
||||||
),
|
|
||||||
itemData: itemData),
|
// 入库时所属项目
|
||||||
// 产品
|
buildTableCell(
|
||||||
buildTableCell(
|
Column(
|
||||||
Column(
|
mainAxisAlignment:
|
||||||
mainAxisAlignment:
|
MainAxisAlignment.center,
|
||||||
MainAxisAlignment.center,
|
crossAxisAlignment:
|
||||||
crossAxisAlignment:
|
CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
|
'${itemData.project?.name}',
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
itemData: itemData),
|
||||||
|
// 产品
|
||||||
|
buildTableCell(
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${itemData.product?.name}',
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${itemData.product?.company?.name}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
ScreenAdaper.height(15),
|
||||||
|
color: AppTheme.grey),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
itemData: itemData),
|
||||||
|
// 规格
|
||||||
|
buildTableCell(
|
||||||
Text(
|
Text(
|
||||||
'${itemData.product?.name}',
|
itemData.product
|
||||||
|
?.productSpecification ??
|
||||||
|
'',
|
||||||
|
style: textStyle),
|
||||||
|
itemData: itemData),
|
||||||
|
// 单价
|
||||||
|
buildTableCell(
|
||||||
|
Text(
|
||||||
|
'¥${double.parse('${itemData.unitPrice}')}',
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
Text(
|
itemData: itemData),
|
||||||
'${itemData.product?.company?.name}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: ScreenAdaper.height(15),
|
|
||||||
color: AppTheme.grey),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
itemData: itemData),
|
|
||||||
// 规格
|
|
||||||
buildTableCell(
|
|
||||||
Text(
|
|
||||||
itemData.product
|
|
||||||
?.productSpecification ??
|
|
||||||
'',
|
|
||||||
style: textStyle),
|
|
||||||
itemData: itemData),
|
|
||||||
// 单价
|
|
||||||
buildTableCell(
|
|
||||||
Text(
|
|
||||||
'¥${double.parse('${itemData.unitPrice}')}',
|
|
||||||
style: textStyle,
|
|
||||||
),
|
|
||||||
itemData: itemData),
|
|
||||||
|
|
||||||
// 库存数量
|
// 库存数量
|
||||||
buildTableCell(
|
buildTableCell(
|
||||||
Text(
|
Text(
|
||||||
'${itemData.quantity}${itemData.product?.unit?.label ?? ''}',
|
'${itemData.quantity}${itemData.product?.unit?.label ?? ''}',
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: textStyle),
|
style: textStyle),
|
||||||
itemData: itemData,
|
itemData: itemData,
|
||||||
alignment: Alignment.centerRight),
|
alignment: Alignment.centerRight),
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
])));
|
]))));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTableCell(Widget child,
|
Widget buildTableCell(Widget child,
|
||||||
|
@ -421,6 +423,11 @@ class InventorySearch extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: ScreenAdaper.sp(30), color: AppTheme.grey),
|
fontSize: ScreenAdaper.sp(30), color: AppTheme.grey),
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
'${itemData.project?.name}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: ScreenAdaper.sp(30), color: AppTheme.grey),
|
||||||
|
),
|
||||||
Text('¥${double.parse('${itemData.unitPrice}')}',
|
Text('¥${double.parse('${itemData.unitPrice}')}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: ScreenAdaper.sp(30), color: AppTheme.grey))
|
fontSize: ScreenAdaper.sp(30), color: AppTheme.grey))
|
||||||
|
|
|
@ -19,6 +19,7 @@ import 'package:sk_base_mobile/widgets/core/sk_date_picker.dart';
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
||||||
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
import 'package:sk_base_mobile/widgets/gradient_button.dart';
|
||||||
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
|
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
|
||||||
|
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||||
|
|
||||||
class NewInventoryInout extends StatelessWidget {
|
class NewInventoryInout extends StatelessWidget {
|
||||||
final NewInventoryInoutController controller;
|
final NewInventoryInoutController controller;
|
||||||
|
@ -32,11 +33,8 @@ class NewInventoryInout extends StatelessWidget {
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: false, // 设置为false以避免保留顶部状态栏的空间
|
top: false, // 设置为false以避免保留顶部状态栏的空间
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: SkAppbar(
|
||||||
title: Text(
|
title: inOrOut == InventoryInOrOutEnum.In ? '入库登记' : '出库登记',
|
||||||
inOrOut == InventoryInOrOutEnum.In ? '入库登记' : '出库登记',
|
|
||||||
style: TextStyle(fontSize: ScreenAdaper.height(35)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
|
|
|
@ -1,692 +0,0 @@
|
||||||
// ignore_for_file: avoid_print
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:data_table_2/data_table_2.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.
|
|
||||||
|
|
||||||
// The file was extracted from GitHub: https://github.com/flutter/gallery
|
|
||||||
// Changes and modifications by Maxim Saplin, 2021
|
|
||||||
|
|
||||||
/// Keeps track of selected rows, feed the data into DesertsDataSource
|
|
||||||
class RestorableDessertSelections extends RestorableProperty<Set<int>> {
|
|
||||||
Set<int> _dessertSelections = {};
|
|
||||||
|
|
||||||
/// Returns whether or not a dessert row is selected by index.
|
|
||||||
bool isSelected(int index) => _dessertSelections.contains(index);
|
|
||||||
|
|
||||||
/// Takes a list of [Dessert]s and saves the row indices of selected rows
|
|
||||||
/// into a [Set].
|
|
||||||
void setDessertSelections(List<Dessert> desserts) {
|
|
||||||
final updatedSet = <int>{};
|
|
||||||
for (var i = 0; i < desserts.length; i += 1) {
|
|
||||||
var dessert = desserts[i];
|
|
||||||
if (dessert.selected) {
|
|
||||||
updatedSet.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_dessertSelections = updatedSet;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<int> createDefaultValue() => _dessertSelections;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<int> fromPrimitives(Object? data) {
|
|
||||||
final selectedItemIndices = data as List<dynamic>;
|
|
||||||
_dessertSelections = {
|
|
||||||
...selectedItemIndices.map<int>((dynamic id) => id as int),
|
|
||||||
};
|
|
||||||
return _dessertSelections;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initWithValue(Set<int> value) {
|
|
||||||
_dessertSelections = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Object toPrimitives() => _dessertSelections.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
int _idCounter = 0;
|
|
||||||
|
|
||||||
/// Domain model entity
|
|
||||||
class Dessert {
|
|
||||||
Dessert(
|
|
||||||
this.name,
|
|
||||||
this.calories,
|
|
||||||
this.fat,
|
|
||||||
this.carbs,
|
|
||||||
this.protein,
|
|
||||||
this.sodium,
|
|
||||||
this.calcium,
|
|
||||||
this.iron,
|
|
||||||
);
|
|
||||||
|
|
||||||
final int id = _idCounter++;
|
|
||||||
|
|
||||||
final String name;
|
|
||||||
final int calories;
|
|
||||||
final double fat;
|
|
||||||
final int carbs;
|
|
||||||
final double protein;
|
|
||||||
final int sodium;
|
|
||||||
final int calcium;
|
|
||||||
final int iron;
|
|
||||||
bool selected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data source implementing standard Flutter's DataTableSource abstract class
|
|
||||||
/// which is part of DataTable and PaginatedDataTable synchronous data fecthin API.
|
|
||||||
/// This class uses static collection of deserts as a data store, projects it into
|
|
||||||
/// DataRows, keeps track of selected items, provides sprting capability
|
|
||||||
class DessertDataSource extends DataTableSource {
|
|
||||||
DessertDataSource.empty(this.context) {
|
|
||||||
desserts = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
DessertDataSource(this.context,
|
|
||||||
[sortedByCalories = false,
|
|
||||||
this.hasRowTaps = false,
|
|
||||||
this.hasRowHeightOverrides = false,
|
|
||||||
this.hasZebraStripes = false]) {
|
|
||||||
desserts = _desserts;
|
|
||||||
if (sortedByCalories) {
|
|
||||||
sort((d) => d.calories, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final BuildContext context;
|
|
||||||
late List<Dessert> desserts;
|
|
||||||
// Add row tap handlers and show snackbar
|
|
||||||
bool hasRowTaps = false;
|
|
||||||
// Override height values for certain rows
|
|
||||||
bool hasRowHeightOverrides = false;
|
|
||||||
// Color each Row by index's parity
|
|
||||||
bool hasZebraStripes = false;
|
|
||||||
|
|
||||||
void sort<T>(Comparable<T> Function(Dessert d) getField, bool ascending) {
|
|
||||||
desserts.sort((a, b) {
|
|
||||||
final aValue = getField(a);
|
|
||||||
final bValue = getField(b);
|
|
||||||
return ascending
|
|
||||||
? Comparable.compare(aValue, bValue)
|
|
||||||
: Comparable.compare(bValue, aValue);
|
|
||||||
});
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSelectedDesserts(RestorableDessertSelections selectedRows) {
|
|
||||||
_selectedCount = 0;
|
|
||||||
for (var i = 0; i < desserts.length; i += 1) {
|
|
||||||
var dessert = desserts[i];
|
|
||||||
if (selectedRows.isSelected(i)) {
|
|
||||||
dessert.selected = true;
|
|
||||||
_selectedCount += 1;
|
|
||||||
} else {
|
|
||||||
dessert.selected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
DataRow2 getRow(int index, [Color? color]) {
|
|
||||||
final format = NumberFormat.decimalPercentPattern(
|
|
||||||
locale: 'en',
|
|
||||||
decimalDigits: 0,
|
|
||||||
);
|
|
||||||
assert(index >= 0);
|
|
||||||
if (index >= desserts.length) throw 'index > _desserts.length';
|
|
||||||
final dessert = desserts[index];
|
|
||||||
return DataRow2.byIndex(
|
|
||||||
index: index,
|
|
||||||
selected: dessert.selected,
|
|
||||||
color: color != null
|
|
||||||
? MaterialStateProperty.all(color)
|
|
||||||
: (hasZebraStripes && index.isEven
|
|
||||||
? MaterialStateProperty.all(Theme.of(context).highlightColor)
|
|
||||||
: null),
|
|
||||||
onSelectChanged: (value) {
|
|
||||||
if (dessert.selected != value) {
|
|
||||||
_selectedCount += value! ? 1 : -1;
|
|
||||||
assert(_selectedCount >= 0);
|
|
||||||
dessert.selected = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTap: hasRowTaps
|
|
||||||
? () => _showSnackbar(context, 'Tapped on row ${dessert.name}')
|
|
||||||
: null,
|
|
||||||
onDoubleTap: hasRowTaps
|
|
||||||
? () => _showSnackbar(context, 'Double Tapped on row ${dessert.name}')
|
|
||||||
: null,
|
|
||||||
onLongPress: hasRowTaps
|
|
||||||
? () => _showSnackbar(context, 'Long pressed on row ${dessert.name}')
|
|
||||||
: null,
|
|
||||||
onSecondaryTap: hasRowTaps
|
|
||||||
? () => _showSnackbar(context, 'Right clicked on row ${dessert.name}')
|
|
||||||
: null,
|
|
||||||
onSecondaryTapDown: hasRowTaps
|
|
||||||
? (d) =>
|
|
||||||
_showSnackbar(context, 'Right button down on row ${dessert.name}')
|
|
||||||
: null,
|
|
||||||
specificRowHeight:
|
|
||||||
hasRowHeightOverrides && dessert.fat >= 25 ? 100 : null,
|
|
||||||
cells: [
|
|
||||||
DataCell(Text(dessert.name)),
|
|
||||||
DataCell(Text('${dessert.calories}'),
|
|
||||||
onTap: () => _showSnackbar(context,
|
|
||||||
'Tapped on a cell with "${dessert.calories}"', Colors.red)),
|
|
||||||
DataCell(Text(dessert.fat.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.carbs}')),
|
|
||||||
DataCell(Text(dessert.protein.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.sodium}')),
|
|
||||||
DataCell(Text(format.format(dessert.calcium / 100))),
|
|
||||||
DataCell(Text(format.format(dessert.iron / 100))),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get rowCount => desserts.length;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isRowCountApproximate => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get selectedRowCount => _selectedCount;
|
|
||||||
|
|
||||||
void selectAll(bool? checked) {
|
|
||||||
for (final dessert in desserts) {
|
|
||||||
dessert.selected = checked ?? false;
|
|
||||||
}
|
|
||||||
_selectedCount = (checked ?? false) ? desserts.length : 0;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Async datasource for AsynPaginatedDataTabke2 example. Based on AsyncDataTableSource which
|
|
||||||
/// is an extension to Flutter's DataTableSource and aimed at solving
|
|
||||||
/// saync data fetching scenarious by paginated table (such as using Web API)
|
|
||||||
class DessertDataSourceAsync extends AsyncDataTableSource {
|
|
||||||
DessertDataSourceAsync() {
|
|
||||||
print('DessertDataSourceAsync created');
|
|
||||||
}
|
|
||||||
|
|
||||||
DessertDataSourceAsync.empty() {
|
|
||||||
_empty = true;
|
|
||||||
print('DessertDataSourceAsync.empty created');
|
|
||||||
}
|
|
||||||
|
|
||||||
DessertDataSourceAsync.error() {
|
|
||||||
_errorCounter = 0;
|
|
||||||
print('DessertDataSourceAsync.error created');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _empty = false;
|
|
||||||
int? _errorCounter;
|
|
||||||
|
|
||||||
RangeValues? _caloriesFilter;
|
|
||||||
|
|
||||||
RangeValues? get caloriesFilter => _caloriesFilter;
|
|
||||||
set caloriesFilter(RangeValues? calories) {
|
|
||||||
_caloriesFilter = calories;
|
|
||||||
refreshDatasource();
|
|
||||||
}
|
|
||||||
|
|
||||||
final DesertsFakeWebService _repo = DesertsFakeWebService();
|
|
||||||
|
|
||||||
String _sortColumn = "name";
|
|
||||||
bool _sortAscending = true;
|
|
||||||
|
|
||||||
void sort(String columnName, bool ascending) {
|
|
||||||
_sortColumn = columnName;
|
|
||||||
_sortAscending = ascending;
|
|
||||||
refreshDatasource();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getTotalRecords() {
|
|
||||||
return Future<int>.delayed(
|
|
||||||
const Duration(milliseconds: 0), () => _empty ? 0 : _dessertsX3.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<AsyncRowsResponse> getRows(int startIndex, int count) async {
|
|
||||||
print('getRows($startIndex, $count)');
|
|
||||||
if (_errorCounter != null) {
|
|
||||||
_errorCounter = _errorCounter! + 1;
|
|
||||||
|
|
||||||
if (_errorCounter! % 2 == 1) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 1000));
|
|
||||||
throw 'Error #${((_errorCounter! - 1) / 2).round() + 1} has occured';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final format = NumberFormat.decimalPercentPattern(
|
|
||||||
locale: 'en',
|
|
||||||
decimalDigits: 0,
|
|
||||||
);
|
|
||||||
assert(startIndex >= 0);
|
|
||||||
|
|
||||||
// List returned will be empty is there're fewer items than startingAt
|
|
||||||
var x = _empty
|
|
||||||
? await Future.delayed(const Duration(milliseconds: 2000),
|
|
||||||
() => DesertsFakeWebServiceResponse(0, []))
|
|
||||||
: await _repo.getData(
|
|
||||||
startIndex, count, _caloriesFilter, _sortColumn, _sortAscending);
|
|
||||||
|
|
||||||
var r = AsyncRowsResponse(
|
|
||||||
x.totalRecords,
|
|
||||||
x.data.map((dessert) {
|
|
||||||
return DataRow(
|
|
||||||
key: ValueKey<int>(dessert.id),
|
|
||||||
//selected: dessert.selected,
|
|
||||||
onSelectChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
setRowSelection(ValueKey<int>(dessert.id), value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cells: [
|
|
||||||
DataCell(Text(dessert.name)),
|
|
||||||
DataCell(Text('${dessert.calories}')),
|
|
||||||
DataCell(Text(dessert.fat.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.carbs}')),
|
|
||||||
DataCell(Text(dessert.protein.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.sodium}')),
|
|
||||||
DataCell(Text(format.format(dessert.calcium / 100))),
|
|
||||||
DataCell(Text(format.format(dessert.iron / 100))),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList());
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DesertsFakeWebServiceResponse {
|
|
||||||
DesertsFakeWebServiceResponse(this.totalRecords, this.data);
|
|
||||||
|
|
||||||
/// THe total ammount of records on the server, e.g. 100
|
|
||||||
final int totalRecords;
|
|
||||||
|
|
||||||
/// One page, e.g. 10 reocrds
|
|
||||||
final List<Dessert> data;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DesertsFakeWebService {
|
|
||||||
int Function(Dessert, Dessert)? _getComparisonFunction(
|
|
||||||
String column, bool ascending) {
|
|
||||||
var coef = ascending ? 1 : -1;
|
|
||||||
switch (column) {
|
|
||||||
case 'name':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * d1.name.compareTo(d2.name);
|
|
||||||
case 'calories':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.calories - d2.calories);
|
|
||||||
case 'fat':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.fat - d2.fat).round();
|
|
||||||
case 'carbs':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.carbs - d2.carbs);
|
|
||||||
case 'protein':
|
|
||||||
return (Dessert d1, Dessert d2) =>
|
|
||||||
coef * (d1.protein - d2.protein).round();
|
|
||||||
case 'sodium':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.sodium - d2.sodium);
|
|
||||||
case 'calcium':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.calcium - d2.calcium);
|
|
||||||
case 'iron':
|
|
||||||
return (Dessert d1, Dessert d2) => coef * (d1.iron - d2.iron);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DesertsFakeWebServiceResponse> getData(int startingAt, int count,
|
|
||||||
RangeValues? caloriesFilter, String sortedBy, bool sortedAsc) async {
|
|
||||||
return Future.delayed(
|
|
||||||
Duration(
|
|
||||||
milliseconds: startingAt == 0
|
|
||||||
? 2650
|
|
||||||
: startingAt < 20
|
|
||||||
? 2000
|
|
||||||
: 400), () {
|
|
||||||
var result = _dessertsX3;
|
|
||||||
|
|
||||||
if (caloriesFilter != null) {
|
|
||||||
result = result
|
|
||||||
.where((e) =>
|
|
||||||
e.calories >= caloriesFilter.start &&
|
|
||||||
e.calories <= caloriesFilter.end)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.sort(_getComparisonFunction(sortedBy, sortedAsc));
|
|
||||||
return DesertsFakeWebServiceResponse(
|
|
||||||
result.length, result.skip(startingAt).take(count).toList());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int _selectedCount = 0;
|
|
||||||
|
|
||||||
List<Dessert> _desserts = <Dessert>[
|
|
||||||
Dessert(
|
|
||||||
'Frozen Yogurt',
|
|
||||||
159,
|
|
||||||
6.0,
|
|
||||||
24,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich',
|
|
||||||
237,
|
|
||||||
9.0,
|
|
||||||
37,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair',
|
|
||||||
262,
|
|
||||||
16.0,
|
|
||||||
24,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake',
|
|
||||||
305,
|
|
||||||
3.7,
|
|
||||||
67,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gingerbread',
|
|
||||||
356,
|
|
||||||
16.0,
|
|
||||||
49,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean',
|
|
||||||
375,
|
|
||||||
0.0,
|
|
||||||
94,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop',
|
|
||||||
392,
|
|
||||||
0.2,
|
|
||||||
98,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomb',
|
|
||||||
408,
|
|
||||||
3.2,
|
|
||||||
87,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut',
|
|
||||||
452,
|
|
||||||
25.0,
|
|
||||||
51,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple Pie',
|
|
||||||
518,
|
|
||||||
26.0,
|
|
||||||
65,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Frozen Yougurt with sugar',
|
|
||||||
168,
|
|
||||||
6.0,
|
|
||||||
26,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich with sugar',
|
|
||||||
246,
|
|
||||||
9.0,
|
|
||||||
39,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair with sugar',
|
|
||||||
271,
|
|
||||||
16.0,
|
|
||||||
26,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake with sugar',
|
|
||||||
314,
|
|
||||||
3.7,
|
|
||||||
69,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gingerbread with sugar',
|
|
||||||
345,
|
|
||||||
16.0,
|
|
||||||
51,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean with sugar',
|
|
||||||
364,
|
|
||||||
0.0,
|
|
||||||
96,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop with sugar',
|
|
||||||
401,
|
|
||||||
0.2,
|
|
||||||
100,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomd with sugar',
|
|
||||||
417,
|
|
||||||
3.2,
|
|
||||||
89,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut with sugar',
|
|
||||||
461,
|
|
||||||
25.0,
|
|
||||||
53,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple pie with sugar',
|
|
||||||
527,
|
|
||||||
26.0,
|
|
||||||
67,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Forzen yougurt with honey',
|
|
||||||
223,
|
|
||||||
6.0,
|
|
||||||
36,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich with honey',
|
|
||||||
301,
|
|
||||||
9.0,
|
|
||||||
49,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair with honey',
|
|
||||||
326,
|
|
||||||
16.0,
|
|
||||||
36,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake with honey',
|
|
||||||
369,
|
|
||||||
3.7,
|
|
||||||
79,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gignerbread with hone',
|
|
||||||
420,
|
|
||||||
16.0,
|
|
||||||
61,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean with honey',
|
|
||||||
439,
|
|
||||||
0.0,
|
|
||||||
106,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop with honey',
|
|
||||||
456,
|
|
||||||
0.2,
|
|
||||||
110,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomd with honey',
|
|
||||||
472,
|
|
||||||
3.2,
|
|
||||||
99,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut with honey',
|
|
||||||
516,
|
|
||||||
25.0,
|
|
||||||
63,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple pie with honey',
|
|
||||||
582,
|
|
||||||
26.0,
|
|
||||||
77,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Dessert> _dessertsX3 = _desserts.toList()
|
|
||||||
..addAll(_desserts.map((i) => Dessert('${i.name} x2', i.calories, i.fat,
|
|
||||||
i.carbs, i.protein, i.sodium, i.calcium, i.iron)))
|
|
||||||
..addAll(_desserts.map((i) => Dessert('${i.name} x3', i.calories, i.fat,
|
|
||||||
i.carbs, i.protein, i.sodium, i.calcium, i.iron)));
|
|
||||||
|
|
||||||
_showSnackbar(BuildContext context, String text, [Color? color]) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
backgroundColor: color,
|
|
||||||
duration: const Duration(seconds: 1),
|
|
||||||
content: Text(text),
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,754 +0,0 @@
|
||||||
import 'package:data_table_2/data_table_2.dart';
|
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
|
||||||
import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
|
|
||||||
import './helper.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
import './data_sources.dart';
|
|
||||||
import './nav_helper.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.
|
|
||||||
|
|
||||||
// The file was extracted from GitHub: https://github.com/flutter/gallery
|
|
||||||
// Changes and modifications by Maxim Saplin, 2021
|
|
||||||
|
|
||||||
class DataTable2FixedNMDemo extends StatefulWidget {
|
|
||||||
const DataTable2FixedNMDemo({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
DataTable2FixedNMDemoState createState() => DataTable2FixedNMDemoState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class DataTable2FixedNMDemoState extends State<DataTable2FixedNMDemo> {
|
|
||||||
bool _sortAscending = true;
|
|
||||||
int? _sortColumnIndex;
|
|
||||||
late DessertDataSource _dessertsDataSource;
|
|
||||||
late DessertDataSourceAsync _asyncDessertsDataSource;
|
|
||||||
bool _initialized = false;
|
|
||||||
final ScrollController _controller = ScrollController();
|
|
||||||
String selectedTableType = dflt;
|
|
||||||
|
|
||||||
int _fixedRows = 1;
|
|
||||||
int _fixedCols = 1;
|
|
||||||
int _dataItems = 30;
|
|
||||||
|
|
||||||
DataRow _getRow(int index, [Color? color]) {
|
|
||||||
final format = NumberFormat.decimalPercentPattern(
|
|
||||||
locale: 'zh',
|
|
||||||
decimalDigits: 0,
|
|
||||||
);
|
|
||||||
assert(index >= 0);
|
|
||||||
if (index >= _desserts.length) throw 'index > _desserts.length';
|
|
||||||
final dessert = _desserts[index];
|
|
||||||
return DataRow2.byIndex(
|
|
||||||
index: index,
|
|
||||||
// selected: dessert.selected,
|
|
||||||
color: color != null ? MaterialStateProperty.all(color) : null,
|
|
||||||
// onSelectChanged: (value) {
|
|
||||||
// if (dessert.selected != value) {
|
|
||||||
// dessert.selected = value!;
|
|
||||||
// setState(() {});
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
cells: [
|
|
||||||
DataCell(SkTextInput(textController: TextEditingController())),
|
|
||||||
DataCell(Text('${dessert.calories}')),
|
|
||||||
DataCell(Text(dessert.fat.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.carbs}')),
|
|
||||||
DataCell(Text(dessert.protein.toStringAsFixed(1))),
|
|
||||||
DataCell(Text('${dessert.sodium}')),
|
|
||||||
DataCell(Text(format.format(dessert.calcium / 100))),
|
|
||||||
DataCell(Text(format.format(dessert.iron / 100))),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectAll(bool? checked) {
|
|
||||||
for (final dessert in _desserts) {
|
|
||||||
dessert.selected = checked ?? false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
if (!_initialized) {
|
|
||||||
_dessertsDataSource = DessertDataSource(context);
|
|
||||||
_asyncDessertsDataSource = DessertDataSourceAsync();
|
|
||||||
_initialized = true;
|
|
||||||
_dessertsDataSource.addListener(() {
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _sort<T>(
|
|
||||||
Comparable<T> Function(Dessert d) getField,
|
|
||||||
int columnIndex,
|
|
||||||
bool ascending,
|
|
||||||
) {
|
|
||||||
_dessertsDataSource.sort<T>(getField, ascending);
|
|
||||||
setState(() {
|
|
||||||
_sortColumnIndex = columnIndex;
|
|
||||||
_sortAscending = ascending;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getTableFromSelectedType() {
|
|
||||||
switch (getCurrentRouteOption(context)) {
|
|
||||||
case paginatedFixedRowsCols:
|
|
||||||
{
|
|
||||||
return getPaginatedDataTable();
|
|
||||||
}
|
|
||||||
case asyncPaginatedFixedRowsCols:
|
|
||||||
{
|
|
||||||
return getAsyncPaginatedDataTable();
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
return getDataTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_dessertsDataSource.dispose();
|
|
||||||
_controller.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(children: [
|
|
||||||
// _getParamsWidget(context),
|
|
||||||
Flexible(
|
|
||||||
fit: FlexFit.tight,
|
|
||||||
child: Theme(
|
|
||||||
data: ThemeData(dividerColor: Colors.grey[400]),
|
|
||||||
child: getTableFromSelectedType()))
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
DataTable2 getDataTable() {
|
|
||||||
return DataTable2(
|
|
||||||
scrollController: _controller,
|
|
||||||
columnSpacing: 0,
|
|
||||||
bottomMargin: 20,
|
|
||||||
border: TableBorder.all(width: 1.0, color: AppTheme.dividerColor),
|
|
||||||
// headingRowColor: MaterialStateProperty.resolveWith(
|
|
||||||
// (states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
|
|
||||||
// headingRowDecoration: BoxDecoration(
|
|
||||||
// gradient: LinearGradient(
|
|
||||||
// colors: [
|
|
||||||
// Colors.grey[400]!,
|
|
||||||
// Colors.grey[200]!,
|
|
||||||
// ],
|
|
||||||
// begin: Alignment.topCenter,
|
|
||||||
// end: Alignment.bottomCenter,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
fixedColumnsColor: Colors.grey[100],
|
|
||||||
// fixedCornerColor: Colors.grey[400],
|
|
||||||
minWidth: 1000,
|
|
||||||
fixedTopRows: _fixedRows,
|
|
||||||
fixedLeftColumns: _fixedCols,
|
|
||||||
// sortColumnIndex: _sortColumnIndex,
|
|
||||||
// sortAscending: _sortAscending,
|
|
||||||
// onSelectAll: (val) => setState(() => selectAll(val)),
|
|
||||||
columns: [
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('过渡'),
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<String>((d) => d.name, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('名称'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calories, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('规格、型号及说明'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.fat, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('单位'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.carbs, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('数量'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.protein, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('备注'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.sodium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('单价'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calcium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('总价'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.iron, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
rows: List<DataRow>.generate(
|
|
||||||
_dataItems, (index) => _getRow(index, Colors.transparent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getPaginatedDataTable() {
|
|
||||||
return PaginatedDataTable2(
|
|
||||||
scrollController: _controller,
|
|
||||||
columnSpacing: 0,
|
|
||||||
horizontalMargin: 12,
|
|
||||||
border: TableBorder.all(width: 1.0, color: Colors.grey),
|
|
||||||
headingRowColor: MaterialStateProperty.resolveWith(
|
|
||||||
(states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
|
|
||||||
fixedColumnsColor: Colors.grey[300],
|
|
||||||
fixedCornerColor: Colors.grey[400],
|
|
||||||
minWidth: 1000,
|
|
||||||
fixedTopRows: _fixedRows,
|
|
||||||
fixedLeftColumns: _fixedCols,
|
|
||||||
sortColumnIndex: _sortColumnIndex,
|
|
||||||
sortAscending: _sortAscending,
|
|
||||||
onSelectAll: (val) => setState(() => selectAll(val)),
|
|
||||||
columns: [
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Desert'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<String>((d) => d.name, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Calories'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calories, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Fat (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.fat, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Carbs (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.carbs, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Protein (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.protein, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Sodium (mg)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.sodium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Calcium (%)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calcium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Iron (%)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.iron, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
source: _dessertsDataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getAsyncPaginatedDataTable() {
|
|
||||||
return AsyncPaginatedDataTable2(
|
|
||||||
scrollController: _controller,
|
|
||||||
columnSpacing: 0,
|
|
||||||
horizontalMargin: 12,
|
|
||||||
border: TableBorder.all(width: 1.0, color: Colors.grey),
|
|
||||||
headingRowColor: MaterialStateProperty.resolveWith(
|
|
||||||
(states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
|
|
||||||
fixedColumnsColor: Colors.grey[300],
|
|
||||||
fixedCornerColor: Colors.grey[400],
|
|
||||||
minWidth: 1000,
|
|
||||||
fixedTopRows: _fixedRows,
|
|
||||||
fixedLeftColumns: _fixedCols,
|
|
||||||
sortColumnIndex: _sortColumnIndex,
|
|
||||||
sortAscending: _sortAscending,
|
|
||||||
onSelectAll: (val) => setState(() => selectAll(val)),
|
|
||||||
columns: [
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Desert'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<String>((d) => d.name, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Calories'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calories, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Fat (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.fat, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Carbs (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.carbs, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Protein (gm)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.protein, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Sodium (mg)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.sodium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Calcium (%)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.calcium, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
DataColumn2(
|
|
||||||
label: const Text('Iron (%)'),
|
|
||||||
size: ColumnSize.S,
|
|
||||||
numeric: true,
|
|
||||||
onSort: (columnIndex, ascending) =>
|
|
||||||
_sort<num>((d) => d.iron, columnIndex, ascending),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
source: _asyncDessertsDataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
Theme _getParamsWidget(BuildContext context) {
|
|
||||||
const double col1 = 130;
|
|
||||||
const double col2 = 210;
|
|
||||||
return Theme(
|
|
||||||
data: blackSlider(context),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: col1, child: Text('Fixed rows ($_fixedRows)')),
|
|
||||||
SizedBox(
|
|
||||||
width: col2,
|
|
||||||
child: Slider(
|
|
||||||
value: _fixedRows.toDouble(),
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
divisions: 10,
|
|
||||||
onChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_fixedRows = val.toInt();
|
|
||||||
});
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: col1,
|
|
||||||
child: Text('Fixed columns ($_fixedCols)')),
|
|
||||||
SizedBox(
|
|
||||||
width: col2,
|
|
||||||
child: Slider(
|
|
||||||
value: _fixedCols.toDouble(),
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
divisions: 10,
|
|
||||||
onChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_fixedCols = val.toInt();
|
|
||||||
});
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
SizedBox(
|
|
||||||
height: 36,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: col1, child: Text('Data items ($_dataItems)')),
|
|
||||||
SizedBox(
|
|
||||||
width: col2,
|
|
||||||
child: Slider(
|
|
||||||
value: _dataItems.toDouble(),
|
|
||||||
min: 0,
|
|
||||||
max: 30,
|
|
||||||
divisions: 10,
|
|
||||||
onChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_dataItems = val.toInt();
|
|
||||||
});
|
|
||||||
}))
|
|
||||||
],
|
|
||||||
))
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Dessert> _desserts = <Dessert>[
|
|
||||||
Dessert(
|
|
||||||
'Frozen Yogurt',
|
|
||||||
159,
|
|
||||||
6.0,
|
|
||||||
24,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich',
|
|
||||||
237,
|
|
||||||
9.0,
|
|
||||||
37,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair',
|
|
||||||
262,
|
|
||||||
16.0,
|
|
||||||
24,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake',
|
|
||||||
305,
|
|
||||||
3.7,
|
|
||||||
67,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gingerbread',
|
|
||||||
356,
|
|
||||||
16.0,
|
|
||||||
49,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean',
|
|
||||||
375,
|
|
||||||
0.0,
|
|
||||||
94,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop',
|
|
||||||
392,
|
|
||||||
0.2,
|
|
||||||
98,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomb',
|
|
||||||
408,
|
|
||||||
3.2,
|
|
||||||
87,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut',
|
|
||||||
452,
|
|
||||||
25.0,
|
|
||||||
51,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple Pie',
|
|
||||||
518,
|
|
||||||
26.0,
|
|
||||||
65,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Frozen Yougurt with sugar',
|
|
||||||
168,
|
|
||||||
6.0,
|
|
||||||
26,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich with sugar',
|
|
||||||
246,
|
|
||||||
9.0,
|
|
||||||
39,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair with sugar',
|
|
||||||
271,
|
|
||||||
16.0,
|
|
||||||
26,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake with sugar',
|
|
||||||
314,
|
|
||||||
3.7,
|
|
||||||
69,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gingerbread with sugar',
|
|
||||||
345,
|
|
||||||
16.0,
|
|
||||||
51,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean with sugar',
|
|
||||||
364,
|
|
||||||
0.0,
|
|
||||||
96,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop with sugar',
|
|
||||||
401,
|
|
||||||
0.2,
|
|
||||||
100,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomd with sugar',
|
|
||||||
417,
|
|
||||||
3.2,
|
|
||||||
89,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut with sugar',
|
|
||||||
461,
|
|
||||||
25.0,
|
|
||||||
53,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple pie with sugar',
|
|
||||||
527,
|
|
||||||
26.0,
|
|
||||||
67,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Forzen yougurt with honey',
|
|
||||||
223,
|
|
||||||
6.0,
|
|
||||||
36,
|
|
||||||
4.0,
|
|
||||||
87,
|
|
||||||
14,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Ice Cream Sandwich with honey',
|
|
||||||
301,
|
|
||||||
9.0,
|
|
||||||
49,
|
|
||||||
4.3,
|
|
||||||
129,
|
|
||||||
8,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Eclair with honey',
|
|
||||||
326,
|
|
||||||
16.0,
|
|
||||||
36,
|
|
||||||
6.0,
|
|
||||||
337,
|
|
||||||
6,
|
|
||||||
7,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Cupcake with honey',
|
|
||||||
369,
|
|
||||||
3.7,
|
|
||||||
79,
|
|
||||||
4.3,
|
|
||||||
413,
|
|
||||||
3,
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Gignerbread with hone',
|
|
||||||
420,
|
|
||||||
16.0,
|
|
||||||
61,
|
|
||||||
3.9,
|
|
||||||
327,
|
|
||||||
7,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Jelly Bean with honey',
|
|
||||||
439,
|
|
||||||
0.0,
|
|
||||||
106,
|
|
||||||
0.0,
|
|
||||||
50,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Lollipop with honey',
|
|
||||||
456,
|
|
||||||
0.2,
|
|
||||||
110,
|
|
||||||
0.0,
|
|
||||||
38,
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Honeycomd with honey',
|
|
||||||
472,
|
|
||||||
3.2,
|
|
||||||
99,
|
|
||||||
6.5,
|
|
||||||
562,
|
|
||||||
0,
|
|
||||||
45,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Donut with honey',
|
|
||||||
516,
|
|
||||||
25.0,
|
|
||||||
63,
|
|
||||||
4.9,
|
|
||||||
326,
|
|
||||||
2,
|
|
||||||
22,
|
|
||||||
),
|
|
||||||
Dessert(
|
|
||||||
'Apple pie with honey',
|
|
||||||
582,
|
|
||||||
26.0,
|
|
||||||
77,
|
|
||||||
7.0,
|
|
||||||
54,
|
|
||||||
12,
|
|
||||||
6,
|
|
||||||
),
|
|
||||||
];
|
|
|
@ -1,176 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
ThemeData blackSlider(BuildContext context) {
|
|
||||||
return Theme.of(context).copyWith(
|
|
||||||
sliderTheme: SliderThemeData(
|
|
||||||
rangeThumbShape:
|
|
||||||
const RectRangeSliderThumbShape(enabledThumbRadius: 8),
|
|
||||||
thumbShape: const RectSliderThumbShape(enabledThumbRadius: 8),
|
|
||||||
thumbColor: Colors.grey[800],
|
|
||||||
activeTrackColor: Colors.grey[700],
|
|
||||||
inactiveTrackColor: Colors.grey[400],
|
|
||||||
activeTickMarkColor: Colors.white,
|
|
||||||
inactiveTickMarkColor: Colors.white));
|
|
||||||
}
|
|
||||||
|
|
||||||
class RectRangeSliderThumbShape extends RangeSliderThumbShape {
|
|
||||||
const RectRangeSliderThumbShape({
|
|
||||||
this.enabledThumbRadius = 10.0,
|
|
||||||
this.disabledThumbRadius,
|
|
||||||
this.elevation = 1.0,
|
|
||||||
this.pressedElevation = 6.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double enabledThumbRadius;
|
|
||||||
|
|
||||||
final double? disabledThumbRadius;
|
|
||||||
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
|
|
||||||
|
|
||||||
final double elevation;
|
|
||||||
|
|
||||||
final double pressedElevation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
|
||||||
return Size.fromRadius(
|
|
||||||
isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(
|
|
||||||
PaintingContext context,
|
|
||||||
Offset center, {
|
|
||||||
required Animation<double> activationAnimation,
|
|
||||||
required Animation<double> enableAnimation,
|
|
||||||
bool isDiscrete = false,
|
|
||||||
bool isEnabled = false,
|
|
||||||
bool? isOnTop,
|
|
||||||
required SliderThemeData sliderTheme,
|
|
||||||
TextDirection? textDirection,
|
|
||||||
Thumb? thumb,
|
|
||||||
bool? isPressed,
|
|
||||||
}) {
|
|
||||||
assert(sliderTheme.showValueIndicator != null);
|
|
||||||
assert(sliderTheme.overlappingShapeStrokeColor != null);
|
|
||||||
|
|
||||||
final Canvas canvas = context.canvas;
|
|
||||||
final Tween<double> radiusTween = Tween<double>(
|
|
||||||
begin: _disabledThumbRadius,
|
|
||||||
end: enabledThumbRadius,
|
|
||||||
);
|
|
||||||
final ColorTween colorTween = ColorTween(
|
|
||||||
begin: sliderTheme.disabledThumbColor,
|
|
||||||
end: sliderTheme.thumbColor,
|
|
||||||
);
|
|
||||||
final double radius = radiusTween.evaluate(enableAnimation);
|
|
||||||
final Tween<double> elevationTween = Tween<double>(
|
|
||||||
begin: elevation,
|
|
||||||
end: pressedElevation,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isOnTop ?? false) {
|
|
||||||
final Paint strokePaint = Paint()
|
|
||||||
..color = sliderTheme.overlappingShapeStrokeColor!
|
|
||||||
..strokeWidth = 1.0
|
|
||||||
..style = PaintingStyle.stroke;
|
|
||||||
canvas.drawRect(
|
|
||||||
Rect.fromCenter(
|
|
||||||
center: center, width: 2 * radius, height: 2 * radius),
|
|
||||||
strokePaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Color color = colorTween.evaluate(enableAnimation)!;
|
|
||||||
|
|
||||||
final double evaluatedElevation =
|
|
||||||
isPressed! ? elevationTween.evaluate(activationAnimation) : elevation;
|
|
||||||
final Path shadowPath = Path()
|
|
||||||
..addArc(
|
|
||||||
Rect.fromCenter(
|
|
||||||
center: center, width: 2 * radius, height: 2 * radius),
|
|
||||||
0,
|
|
||||||
pi * 2);
|
|
||||||
canvas.drawShadow(shadowPath, Colors.black, evaluatedElevation, true);
|
|
||||||
|
|
||||||
canvas.drawRect(
|
|
||||||
Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
|
|
||||||
Paint()..color = color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RectSliderThumbShape extends SliderComponentShape {
|
|
||||||
const RectSliderThumbShape({
|
|
||||||
this.enabledThumbRadius = 10.0,
|
|
||||||
this.disabledThumbRadius,
|
|
||||||
this.elevation = 1.0,
|
|
||||||
this.pressedElevation = 6.0,
|
|
||||||
});
|
|
||||||
|
|
||||||
final double enabledThumbRadius;
|
|
||||||
|
|
||||||
final double? disabledThumbRadius;
|
|
||||||
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
|
|
||||||
|
|
||||||
final double elevation;
|
|
||||||
|
|
||||||
final double pressedElevation;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
|
|
||||||
return Size.fromRadius(
|
|
||||||
isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(
|
|
||||||
PaintingContext context,
|
|
||||||
Offset center, {
|
|
||||||
required Animation<double> activationAnimation,
|
|
||||||
required Animation<double> enableAnimation,
|
|
||||||
required bool isDiscrete,
|
|
||||||
required TextPainter labelPainter,
|
|
||||||
required RenderBox parentBox,
|
|
||||||
required SliderThemeData sliderTheme,
|
|
||||||
required TextDirection textDirection,
|
|
||||||
required double value,
|
|
||||||
required double textScaleFactor,
|
|
||||||
required Size sizeWithOverflow,
|
|
||||||
}) {
|
|
||||||
assert(sliderTheme.disabledThumbColor != null);
|
|
||||||
assert(sliderTheme.thumbColor != null);
|
|
||||||
|
|
||||||
final Canvas canvas = context.canvas;
|
|
||||||
final Tween<double> radiusTween = Tween<double>(
|
|
||||||
begin: _disabledThumbRadius,
|
|
||||||
end: enabledThumbRadius,
|
|
||||||
);
|
|
||||||
final ColorTween colorTween = ColorTween(
|
|
||||||
begin: sliderTheme.disabledThumbColor,
|
|
||||||
end: sliderTheme.thumbColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
final Color color = colorTween.evaluate(enableAnimation)!;
|
|
||||||
final double radius = radiusTween.evaluate(enableAnimation);
|
|
||||||
|
|
||||||
final Tween<double> elevationTween = Tween<double>(
|
|
||||||
begin: elevation,
|
|
||||||
end: pressedElevation,
|
|
||||||
);
|
|
||||||
|
|
||||||
final double evaluatedElevation =
|
|
||||||
elevationTween.evaluate(activationAnimation);
|
|
||||||
final Path path = Path()
|
|
||||||
..addArc(
|
|
||||||
Rect.fromCenter(
|
|
||||||
center: center, width: 2 * radius, height: 2 * radius),
|
|
||||||
0,
|
|
||||||
pi * 2);
|
|
||||||
canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
|
|
||||||
|
|
||||||
canvas.drawRect(
|
|
||||||
Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
|
|
||||||
Paint()..color = color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Route options are used to configure certain features of
|
|
||||||
/// the given example
|
|
||||||
String getCurrentRouteOption(BuildContext context) {
|
|
||||||
var isEmpty = ModalRoute.of(context) != null &&
|
|
||||||
ModalRoute.of(context)!.settings.arguments != null &&
|
|
||||||
ModalRoute.of(context)!.settings.arguments is String
|
|
||||||
? ModalRoute.of(context)!.settings.arguments as String
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return isEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route options
|
|
||||||
const dflt = 'Default';
|
|
||||||
const noData = 'No data';
|
|
||||||
const autoRows = 'Auto rows';
|
|
||||||
const showBordersWithZebraStripes = 'Borders with Zebra';
|
|
||||||
const custPager = 'Custom pager';
|
|
||||||
const defaultSorting = 'Default sorting';
|
|
||||||
const selectAllPage = 'Select all at page';
|
|
||||||
const rowTaps = 'Row Taps';
|
|
||||||
const rowHeightOverrides = 'Row height overrides';
|
|
||||||
const fixedColumnWidth = 'Fixed column width';
|
|
||||||
const dataTable2 = 'DataTable2';
|
|
||||||
const paginatedFixedRowsCols = 'PaginatedDataTable2';
|
|
||||||
const asyncPaginatedFixedRowsCols = 'AsyncPaginatedDataTable2';
|
|
||||||
const custArrows = 'Custom sort arrows';
|
|
||||||
const asyncErrors =
|
|
||||||
"Errors/Retries"; // Async sample that emulates network error and allow retrying load operation
|
|
||||||
const goToLast =
|
|
||||||
"Start at last page"; // Used by async example, navigates to the very last page upon opening the screen
|
|
||||||
const rounded = 'Rounded style';
|
|
||||||
|
|
||||||
/// Configurations available to given example routes
|
|
||||||
const Map<String, List<String>> routeOptions = {
|
|
||||||
'/datatable2': [
|
|
||||||
dflt,
|
|
||||||
noData,
|
|
||||||
showBordersWithZebraStripes,
|
|
||||||
fixedColumnWidth,
|
|
||||||
rowTaps,
|
|
||||||
rowHeightOverrides,
|
|
||||||
custArrows,
|
|
||||||
rounded
|
|
||||||
],
|
|
||||||
'/paginated2': [dflt, noData, autoRows, custPager, defaultSorting],
|
|
||||||
'/datatable2fixedmn': [
|
|
||||||
dataTable2,
|
|
||||||
paginatedFixedRowsCols,
|
|
||||||
asyncPaginatedFixedRowsCols
|
|
||||||
],
|
|
||||||
'/asyncpaginated2': [
|
|
||||||
dflt,
|
|
||||||
noData,
|
|
||||||
selectAllPage,
|
|
||||||
autoRows,
|
|
||||||
asyncErrors,
|
|
||||||
goToLast,
|
|
||||||
custPager
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
List<String>? getOptionsForRoute(String route) {
|
|
||||||
if (!routeOptions.containsKey(route)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return routeOptions[route];
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:sk_base_mobile/app_theme.dart';
|
import 'package:sk_base_mobile/app_theme.dart';
|
||||||
import 'package:sk_base_mobile/screens/sale_quotation/components/data_table.dart';
|
|
||||||
import 'package:sk_base_mobile/util/modal.util.dart';
|
import 'package:sk_base_mobile/util/modal.util.dart';
|
||||||
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
|
||||||
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
import 'package:sk_base_mobile/widgets/sk_appbar.dart';
|
||||||
|
@ -44,60 +43,63 @@ class SaleQuotationPage extends StatelessWidget {
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: SafeArea(
|
||||||
children: [
|
bottom: ScreenAdaper.isLandspace() ? false : true,
|
||||||
Container(
|
child: Column(
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: headerBgcolor,
|
Container(
|
||||||
border: Border(
|
decoration: BoxDecoration(
|
||||||
bottom: BorderSide(
|
color: headerBgcolor,
|
||||||
color: AppTheme.nearlyBlack.withOpacity(0.5)))),
|
border: Border(
|
||||||
padding: EdgeInsets.only(
|
bottom: BorderSide(
|
||||||
left: ScreenAdaper.width(20),
|
color: AppTheme.nearlyBlack.withOpacity(0.5)))),
|
||||||
top: ScreenAdaper.height(10),
|
padding: EdgeInsets.only(
|
||||||
bottom: ScreenAdaper.height(10)),
|
left: ScreenAdaper.width(20),
|
||||||
child: Row(
|
top: ScreenAdaper.height(10),
|
||||||
children: [
|
bottom: ScreenAdaper.height(10)),
|
||||||
Text(
|
child: Row(
|
||||||
'名称',
|
children: [
|
||||||
style: headerTitleStyle,
|
Text(
|
||||||
),
|
'名称',
|
||||||
const Spacer(),
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
width: quantityWidth,
|
|
||||||
// constraints: BoxConstraints(minWidth: quantityWidth),
|
|
||||||
child: Text(
|
|
||||||
'数量',
|
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
width: unitPriceWidth,
|
width: quantityWidth,
|
||||||
// constraints: BoxConstraints(minWidth: unitPriceWidth),
|
// constraints: BoxConstraints(minWidth: quantityWidth),
|
||||||
child: Text(
|
child: Text(
|
||||||
'单价',
|
'数量',
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
Container(
|
alignment: Alignment.center,
|
||||||
alignment: Alignment.center,
|
width: unitPriceWidth,
|
||||||
width: amountWidth,
|
// constraints: BoxConstraints(minWidth: unitPriceWidth),
|
||||||
child: Text(
|
child: Text(
|
||||||
'总价',
|
'单价',
|
||||||
style: headerTitleStyle,
|
style: headerTitleStyle,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
],
|
alignment: Alignment.center,
|
||||||
)),
|
width: amountWidth,
|
||||||
Expanded(
|
child: Text(
|
||||||
child: Obx(() => CustomScrollView(
|
'总价',
|
||||||
slivers: controller.groups
|
style: headerTitleStyle,
|
||||||
.mapIndexed<Widget>((index, e) => buildBody(e, index))
|
),
|
||||||
.toList(),
|
),
|
||||||
))),
|
],
|
||||||
],
|
)),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() => CustomScrollView(
|
||||||
|
slivers: controller.groups
|
||||||
|
.mapIndexed<Widget>((index, e) => buildBody(e, index))
|
||||||
|
.toList(),
|
||||||
|
))),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sk_base_mobile/util/util.dart';
|
||||||
|
|
||||||
class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
@ -8,7 +9,10 @@ class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(title),
|
title: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(fontSize: ScreenAdaper.sp(40)),
|
||||||
|
),
|
||||||
actions: action,
|
actions: action,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
366
pubspec.lock
366
pubspec.lock
File diff suppressed because it is too large
Load Diff
|
@ -63,7 +63,6 @@ dependencies:
|
||||||
flutter_native_splash: ^2.3.11
|
flutter_native_splash: ^2.3.11
|
||||||
flutter_svg: ^2.0.10+1
|
flutter_svg: ^2.0.10+1
|
||||||
flutter_sticky_header: ^0.6.5
|
flutter_sticky_header: ^0.6.5
|
||||||
data_table_2: ^2.5.12
|
|
||||||
flutter_slidable: ^3.1.0
|
flutter_slidable: ^3.1.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue