From 0a22b5c05149510a27052ac0a9581b8c72eb43aa Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Sun, 16 Jun 2024 23:33:38 +0300 Subject: [PATCH] Feat: order info page --- lib/components/history_item_card.dart | 72 +++---- lib/components/order_detail_item_card.dart | 58 ++++++ lib/interfaces/items.dart | 153 ++++++++++++++ lib/pages/order_history.dart | 25 ++- lib/pages/order_info.dart | 219 +++++++++++++++++++++ 5 files changed, 480 insertions(+), 47 deletions(-) create mode 100644 lib/components/order_detail_item_card.dart create mode 100644 lib/pages/order_info.dart diff --git a/lib/components/history_item_card.dart b/lib/components/history_item_card.dart index fadf338..762eff5 100644 --- a/lib/components/history_item_card.dart +++ b/lib/components/history_item_card.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:gymlink_module_web/pages/order_info.dart'; +import 'package:gymlink_module_web/tools/routes.dart'; enum OrderStatus { created, inProgress, completed, canceled } @@ -15,14 +17,12 @@ class HistoryItemCard extends StatelessWidget { final String cost; final String date; final Image image; - final OrderStatus status; const HistoryItemCard({ super.key, required this.id, required this.cost, required this.date, - required this.status, required this.image, }); @@ -38,38 +38,42 @@ class HistoryItemCard extends StatelessWidget { minWidth: 600, maxWidth: 800, ), - child: Card( - elevation: 4, - color: Theme.of(context).scaffoldBackgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30), - ), - child: Padding( - padding: const EdgeInsetsDirectional.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image, - const SizedBox(width: 20), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MarkdownBody( - data: '### Заказ **№$id** от $date', - ), - MarkdownBody( - data: 'Статус: **${orderStatusMap[status]}**'), - MarkdownBody(data: 'Сумма: **$cost руб.**'), - ], - ) - ], - ), - ], + child: GestureDetector( + onTap: () { + Navigator.of(context).push( + CustomPageRoute(builder: (context) => OrderInfoPage(id: id))); + }, + child: Card( + elevation: 4, + color: Theme.of(context).scaffoldBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + child: Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image, + const SizedBox(width: 20), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MarkdownBody( + data: '### Заказ **№$id** от $date', + ), + MarkdownBody(data: 'Сумма: **$cost руб.**'), + ], + ) + ], + ), + ], + ), ), ), ), diff --git a/lib/components/order_detail_item_card.dart b/lib/components/order_detail_item_card.dart new file mode 100644 index 0000000..85cb4e8 --- /dev/null +++ b/lib/components/order_detail_item_card.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; + +class OrderDetailCardItemCard extends StatelessWidget { + final String name; + final int count; + final double price; + final Widget image; + const OrderDetailCardItemCard( + {super.key, + required this.image, + required this.name, + required this.count, + required this.price}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: + const EdgeInsetsDirectional.symmetric(horizontal: 10, vertical: 10), + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 130), + child: Card( + elevation: 4, + color: Theme.of(context).scaffoldBackgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30), + ), + child: Padding( + padding: const EdgeInsetsDirectional.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image, + const SizedBox(width: 20), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + MarkdownBody(data: '# $name'), + Text( + '${price.toStringAsFixed(2)} руб. x $count = ${(price * count).toStringAsFixed(2)} руб.'), + ], + ) + ], + ), + MarkdownBody(data: '# X$count') + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/interfaces/items.dart b/lib/interfaces/items.dart index 8eb9939..50f98cc 100644 --- a/lib/interfaces/items.dart +++ b/lib/interfaces/items.dart @@ -148,3 +148,156 @@ class GymCategory { "name": name, }; } + +class GymHistoryItem { + final String id; + final String date; + final String sum; + final String photo; + + GymHistoryItem({ + required this.id, + required this.date, + required this.sum, + required this.photo, + }); + + factory GymHistoryItem.fromRawJson(String str) => + GymHistoryItem.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GymHistoryItem.fromJson(Map json) => GymHistoryItem( + id: json["id"], + date: json["date"], + sum: json["sum"], + photo: json["photo"], + ); + + Map toJson() => { + "id": id, + "date": date, + "sum": sum, + "photo": photo, + }; +} + +class GymHistoryItemDetail { + final String id; + final String date; + final String sum; + final String payUrl; + final String receiver; + final String email; + final String address; + final List providers; + + GymHistoryItemDetail({ + required this.id, + required this.date, + required this.sum, + required this.payUrl, + required this.providers, + required this.receiver, + required this.email, + required this.address, + }); + + factory GymHistoryItemDetail.fromRawJson(String str) => + GymHistoryItemDetail.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GymHistoryItemDetail.fromJson(Map json) => + GymHistoryItemDetail( + id: json["id"], + date: json["date"], + sum: json["sum"], + receiver: json["receiver"], + email: json["email"], + address: json["address"], + payUrl: json["pay_url"], + providers: List.from(json["providers"] + .map((x) => GymHistoryItemDetailProvider.fromJson(x))), + ); + + Map toJson() => { + "id": id, + "date": date, + "sum": sum, + "pay_url": payUrl, + "providers": List.from(providers.map((x) => x.toJson())), + "receiver": receiver, + "email": email, + "address": address, + }; +} + +class GymHistoryItemDetailProvider { + final String id; + final String name; + final String status; + final List items; + + GymHistoryItemDetailProvider({ + required this.id, + required this.name, + required this.status, + required this.items, + }); + + factory GymHistoryItemDetailProvider.fromRawJson(String str) => + GymHistoryItemDetailProvider.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GymHistoryItemDetailProvider.fromJson(Map json) => + GymHistoryItemDetailProvider( + id: json["id"], + name: json["name"], + status: json["status"], + items: List.from( + json["items"].map((x) => GymHistoryItemDetailItem.fromJson(x))), + ); + + Map toJson() => { + "id": id, + "name": name, + "status": status, + "items": List.from(items.map((x) => x.toJson())), + }; +} + +class GymHistoryItemDetailItem { + final String photo; + final String id; + final int count; + final String price; + + GymHistoryItemDetailItem({ + required this.photo, + required this.id, + required this.count, + required this.price, + }); + + factory GymHistoryItemDetailItem.fromRawJson(String str) => + GymHistoryItemDetailItem.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GymHistoryItemDetailItem.fromJson(Map json) => + GymHistoryItemDetailItem( + photo: json["photo"], + id: json["id"], + count: json["count"], + price: json["price"], + ); + + Map toJson() => { + "photo": photo, + "id": id, + "count": count, + "price": price, + }; +} diff --git a/lib/pages/order_history.dart b/lib/pages/order_history.dart index 80a0766..540ba55 100644 --- a/lib/pages/order_history.dart +++ b/lib/pages/order_history.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/components/heading.dart'; import 'package:gymlink_module_web/components/history_item_card.dart'; +import 'package:gymlink_module_web/interfaces/items.dart'; import 'package:gymlink_module_web/tools/relative.dart'; import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; @@ -45,13 +46,16 @@ class HistoryPage extends StatefulWidget { } class _HistoryPageState extends State { - List> my_orders = []; + List my_orders = []; late Timer _updateTimer; @override void initState() { super.initState(); - my_orders = orders; + my_orders = [ + GymHistoryItem( + id: '123', date: '01.01.1970', sum: '120', photo: 'product.png') + ]; ordersRefresh(); } @@ -63,12 +67,8 @@ class _HistoryPageState extends State { await Future.delayed(const Duration(milliseconds: 1000)); setState(() { my_orders.add( - { - "image": "product.png", - "price": "120", - "id": "666666", - "date": "11.09.2001" - }, + GymHistoryItem( + id: '123', date: '01.01.1970', sum: '120', photo: 'product.png'), ); }); } @@ -112,14 +112,13 @@ class _HistoryPageState extends State { itemBuilder: (context, index) { final item = my_orders[index]; return HistoryItemCard( - id: item['id']!, - cost: item['price']!, - date: item['date']!, + id: item.id, + cost: item.sum, + date: item.date, image: Image( - image: AssetImage('assets/${item['image']!}'), + image: AssetImage('assets/${item.photo}'), width: 50, ), - status: OrderStatus.completed, ); }, ), diff --git a/lib/pages/order_info.dart b/lib/pages/order_info.dart new file mode 100644 index 0000000..2099432 --- /dev/null +++ b/lib/pages/order_info.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:gymlink_module_web/components/app_bar.dart'; +import 'package:gymlink_module_web/components/heading.dart'; +import 'package:gymlink_module_web/interfaces/items.dart'; +import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; + +final GymHistoryItemDetail item = GymHistoryItemDetail.fromJson({ + "id": "12345", + "date": "01.01.1970", + "sum": "45000", + "pay_url": "url", + "receiver": "Иванов Иван Иванович", + "email": "a@a.ru", + "address": "г. Москва, ул. Пушкина, д. 17", + "providers": [ + { + "id": "123", + "name": "Поставщик 1", + "status": "Доставлен", + "items": [ + {"photo": "url", "id": "123", "count": 2, "price": "15000"}, + {"photo": "url", "id": "123", "count": 2, "price": "15000"} + ] + }, + { + "id": "123", + "name": "Поставщик 1", + "status": "Доставляется", + "items": [ + {"photo": "url", "id": "123", "count": 2, "price": "15000"} + ] + } + ] +}); + +//FIXME: Починить скролл +class OrderInfoPage extends StatefulWidget { + final String id; + const OrderInfoPage({super.key, required this.id}); + + @override + State createState() => _OrderInfoPageState(); +} + +class _OrderInfoPageState extends State { + GymHistoryItemDetail? detail; + final _scrollController = ScrollController(); + @override + void initState() { + super.initState(); + setState(() { + detail = item; + }); + debugPrint("AAAAAAAAAAAA${detail?.toRawJson()}"); + } + + Future _onRefresh() { + return Future.delayed(const Duration(milliseconds: 1000)); + } + + Widget _buildContent() { + return Column( + children: [ + GymLinkHeader(title: "Заказ #${detail?.id} от ${detail?.date}"), + Expanded( + child: LazyLoadScrollView( + onEndOfPage: _onRefresh, + child: Scrollbar( + controller: _scrollController, + child: ListView( + controller: _scrollController, + children: [ + ListView.builder( + itemCount: 3, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + final provider = detail!.providers[0]; + return const Card( + child: Text('test'), + ); + }, + ), + Expanded( + child: Card( + elevation: 4, + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 10, vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MarkdownBody( + data: + "### Адрес получателя: _${detail!.address}_"), + Markdown(data: '### Почта: _${detail!.email}_'), + // Markdown( + // data: '### ФИО: _${detail!.receiver}_'), + ], + ), + ), + ), + ), + ], + ), + ))), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const GymLinkAppBar(), + body: detail == null + ? const Center(child: CircularProgressIndicator()) + : _buildContent(), + ); + } + + // @override + // Widget build(BuildContext context) { + // return Scaffold( + // appBar: const GymLinkAppBar(), + // body: detail == null + // ? const Center(child: CircularProgressIndicator()) + // : Column( + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // GymLinkHeader(title: "Заказ #${detail?.id} от ${detail?.date}"), + // Expanded( + // child: RefreshIndicator( + // onRefresh: () => _onRefresh(), + // edgeOffset: 55, + // child: Scrollbar( + // controller: _scrollController, + // child: ListView( + // shrinkWrap: true, + // controller: _scrollController, + // children: [ + // detail != null + // ? ListView.builder( + // itemCount: (detail!.providers.length + 1), + // shrinkWrap: true, + // physics: const NeverScrollableScrollPhysics(), + // itemBuilder: (context, index) { + // final provider = detail!.providers[0]; + // return Card( + // elevation: 3, + // child: Column( + // children: [ + // MarkdownBody( + // data: '# ${provider.name}'), + // MarkdownBody( + // data: + // '## Статус: ${provider.status}'), + // const MarkdownBody( + // data: '### Состав:'), + // for (final item in provider.items) + // OrderDetailCardItemCard( + // image: FutureBuilder( + // future: precacheImage( + // const AssetImage( + // 'assets/product.png'), + // context), + // builder: (context, snapshot) { + // if (snapshot + // .connectionState == + // ConnectionState.done) { + // return const Image( + // image: AssetImage( + // 'assets/product.png'), + // width: 50, + // ); + // } else { + // return const CircularProgressIndicator(); + // } + // }, + // ), + // name: 'Протеин', + // count: item.count, + // price: double.parse(item.price), + // ), + // ], + // ), + // ); + // }, + // ) + // : const SizedBox.shrink(), + // Card( + // elevation: 4, + // child: Padding( + // padding: const EdgeInsetsDirectional.symmetric( + // horizontal: 10, vertical: 20), + // child: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // MarkdownBody( + // data: + // "### Адрес получателя: _${detail!.address}_"), + // Markdown( + // data: '### Почта: _${detail!.email}_'), + // // Markdown( + // // data: '### ФИО: _${detail!.receiver}_'), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + // ), + // ), + // ], + // ), + // ); + // } +}