From bdcd4507c2bfeeccec37a1280039d6dd106b4650 Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Tue, 18 Jun 2024 15:14:37 +0300 Subject: [PATCH] feat: fake orders --- lib/interfaces/items.dart | 4 +- lib/pages/order_confirmation.dart | 73 +++++++++++++++++ lib/pages/order_history.dart | 127 ++++++++++++++++++++---------- lib/pages/order_info.dart | 28 +++++-- lib/tools/history.dart | 108 +++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 52 deletions(-) create mode 100644 lib/tools/history.dart diff --git a/lib/interfaces/items.dart b/lib/interfaces/items.dart index b210cff..4ac47c8 100644 --- a/lib/interfaces/items.dart +++ b/lib/interfaces/items.dart @@ -150,7 +150,7 @@ class GymCategory { } class GymHistoryItem { - final String id; + String id; final String date; final String sum; final String photo; @@ -216,7 +216,7 @@ class GymHistoryItemDetail { receiver: json["receiver"], email: json["email"], address: json["address"], - payUrl: json["pay_url"], + payUrl: json["pay_url"] as String?, providers: List.from(json["providers"] .map((x) => GymHistoryItemDetailProvider.fromJson(x))), ); diff --git a/lib/pages/order_confirmation.dart b/lib/pages/order_confirmation.dart index 520470d..993519c 100644 --- a/lib/pages/order_confirmation.dart +++ b/lib/pages/order_confirmation.dart @@ -6,6 +6,7 @@ import 'package:gymlink_module_web/components/order_confirm_item_card.dart'; import 'package:gymlink_module_web/interfaces/items.dart'; import 'package:gymlink_module_web/pages/order_history.dart'; import 'package:gymlink_module_web/providers/cart.dart'; +import 'package:gymlink_module_web/tools/history.dart'; import 'package:gymlink_module_web/tools/items.dart'; import 'package:gymlink_module_web/tools/prefs.dart'; import 'package:gymlink_module_web/tools/routes.dart'; @@ -64,6 +65,9 @@ class _OrderConfirmationPageState extends State { List gymCart = []; bool isAgree = false; bool _isLoading = true; + final _emailController = TextEditingController(); + final _addressController = TextEditingController(); + final _nameController = TextEditingController(); @override void initState() { @@ -93,6 +97,65 @@ class _OrderConfirmationPageState extends State { } } + bool _checkInputs() { + final email = _emailController.text; + final address = _addressController.text; + final name = _nameController.text; + + if (!RegExp(r"^((?!\.)[\w\-_.]*[^.])(@\w+)(\.\w+(\.\w+)?[^.\W])$") + .hasMatch(email)) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('Некорректный email'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('ОК'), + ) + ], + ), + ); + return false; + } + + if (address.isEmpty) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('Адрес не может быть пустым'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('ОК'), + ) + ], + ), + ); + return false; + } + + if (name.isEmpty) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Ошибка'), + content: const Text('ФИО не может быть пустым'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('ОК'), + ) + ], + ), + ); + return false; + } + return true; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -148,6 +211,7 @@ class _OrderConfirmationPageState extends State { data: '## Итого: ${totalPrice.toStringAsFixed(2)} руб.'), Expanded( child: TextField( + controller: _addressController, decoration: InputDecoration( hintText: 'Адрес доставки', border: OutlineInputBorder( @@ -158,6 +222,7 @@ class _OrderConfirmationPageState extends State { ), Expanded( child: TextField( + controller: _emailController, decoration: InputDecoration( hintText: 'Электронная почта', border: OutlineInputBorder( @@ -169,6 +234,7 @@ class _OrderConfirmationPageState extends State { ), Expanded( child: TextField( + controller: _nameController, decoration: InputDecoration( hintText: 'Получатель', border: OutlineInputBorder( @@ -179,8 +245,15 @@ class _OrderConfirmationPageState extends State { ), ElevatedButton( onPressed: () async { + if (!_checkInputs()) return; _goToPage(); await clearCart(); + await addToHistory(GymHistoryItem( + id: '123', + date: '01.01.1970', + sum: '123', + photo: 'product.png', + )); Provider.of(context, listen: false) .updateCartLength(); Navigator.of(context).pushAndRemoveUntil( diff --git a/lib/pages/order_history.dart b/lib/pages/order_history.dart index 540ba55..ecac66b 100644 --- a/lib/pages/order_history.dart +++ b/lib/pages/order_history.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; 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/history.dart'; import 'package:gymlink_module_web/tools/relative.dart'; import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; @@ -48,29 +50,22 @@ class HistoryPage extends StatefulWidget { class _HistoryPageState extends State { List my_orders = []; late Timer _updateTimer; + bool _isLoading = true; + bool _isRefreshing = false; @override void initState() { super.initState(); - my_orders = [ - GymHistoryItem( - id: '123', date: '01.01.1970', sum: '120', photo: 'product.png') - ]; ordersRefresh(); } void ordersRefresh() { _updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh); + Future.microtask(() => _onRefresh(_updateTimer)); } void _onLoad() async { await Future.delayed(const Duration(milliseconds: 1000)); - setState(() { - my_orders.add( - GymHistoryItem( - id: '123', date: '01.01.1970', sum: '120', photo: 'product.png'), - ); - }); } @override @@ -81,7 +76,12 @@ class _HistoryPageState extends State { Future _onRefresh(Timer timer) async { await Future.delayed(const Duration(milliseconds: 1000)); - debugPrint('refreshed'); + var orders = await getHistory(); + setState(() { + my_orders = orders; + _isLoading = false; + _isRefreshing = false; + }); } @override @@ -95,42 +95,83 @@ class _HistoryPageState extends State { title: 'История заказов', toMain: true, ), - Expanded( - child: Row( - children: [ - Expanded( - child: LazyLoadScrollView( - onEndOfPage: _onLoad, - scrollOffset: 200, - child: RefreshIndicator( - edgeOffset: 55, - onRefresh: () => _onRefresh(_updateTimer), - child: Stack( - children: [ - ListView.builder( - itemCount: my_orders.length, - itemBuilder: (context, index) { - final item = my_orders[index]; - return HistoryItemCard( - id: item.id, - cost: item.sum, - date: item.date, - image: Image( - image: AssetImage('assets/${item.photo}'), - width: 50, - ), - ); - }, - ), - ], + const SizedBox(height: 5), + kIsWeb && !_isLoading + ? Center( + child: ElevatedButton( + onPressed: () { + setState(() => _isRefreshing = true); + _onRefresh(_updateTimer); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(50)), ), + foregroundColor: Colors.white, + fixedSize: const Size(180, 40), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Обновить'), + Spacer(), + Icon(Icons.refresh), + Spacer() + ], ), ), + ) + : const SizedBox.shrink(), + const SizedBox(height: 5), + _isRefreshing + ? const Center(child: CircularProgressIndicator()) + : const SizedBox.shrink(), + const SizedBox(height: 5), + _isLoading + ? const Expanded( + child: Center(child: CircularProgressIndicator())) + : Expanded( + child: Row( + children: [ + Expanded( + child: LazyLoadScrollView( + onEndOfPage: _onLoad, + scrollOffset: 200, + child: RefreshIndicator( + edgeOffset: 55, + onRefresh: () => _onRefresh(_updateTimer), + child: my_orders.isEmpty + ? const Center(child: Text('Нет заказов')) + : Stack( + children: [ + ListView.builder( + itemCount: my_orders.length, + itemBuilder: (context, index) { + final item = my_orders[index]; + return HistoryItemCard( + id: item.id, + cost: item.sum, + date: item.date, + image: Image( + image: AssetImage( + 'assets/${item.photo}'), + width: 50, + ), + ); + }, + ), + ], + ), + ), + ), + ), + my_orders.isEmpty + ? const SizedBox.shrink() + : getSpacer(context: context) + ], + ), ), - getSpacer(context: context) - ], - ), - ), ], ), ); diff --git a/lib/pages/order_info.dart b/lib/pages/order_info.dart index ae3d696..bddce0f 100644 --- a/lib/pages/order_info.dart +++ b/lib/pages/order_info.dart @@ -7,6 +7,7 @@ import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/components/heading.dart'; import 'package:gymlink_module_web/components/order_detail_item_card.dart'; import 'package:gymlink_module_web/interfaces/items.dart'; +import 'package:gymlink_module_web/tools/history.dart'; import 'package:url_launcher/url_launcher.dart'; final GymHistoryItemDetail item = GymHistoryItemDetail.fromJson({ @@ -38,7 +39,6 @@ final GymHistoryItemDetail item = GymHistoryItemDetail.fromJson({ ] }); -//FIXME: Починить скролл class OrderInfoPage extends StatefulWidget { final String id; const OrderInfoPage({super.key, required this.id}); @@ -51,6 +51,7 @@ class _OrderInfoPageState extends State { GymHistoryItemDetail? detail; final _scrollController = ScrollController(); late Timer _updateTimer; + bool _isRefreshing = false; @override void initState() { super.initState(); @@ -65,9 +66,11 @@ class _OrderInfoPageState extends State { } Future _onRefresh(Timer timer) async { - return Future.delayed(const Duration(milliseconds: 1000), () { + return Future.delayed(const Duration(milliseconds: 1000), () async { + var orderInfo = await getHistoryDetail(widget.id); setState(() { - detail = item; + detail = orderInfo; + _isRefreshing = false; }); }); } @@ -100,7 +103,12 @@ class _OrderInfoPageState extends State { kIsWeb ? Center( child: ElevatedButton( - onPressed: () => _onRefresh(_updateTimer), + onPressed: () { + setState(() { + _isRefreshing = true; + }); + _onRefresh(_updateTimer); + }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, shape: const RoundedRectangleBorder( @@ -122,13 +130,19 @@ class _OrderInfoPageState extends State { ), ) : const SizedBox.shrink(), - const SizedBox(height: 10), + const SizedBox(height: 5), + _isRefreshing + ? const Center( + child: CircularProgressIndicator(), + ) + : const SizedBox.shrink(), + const SizedBox(height: 5), ListView.builder( - itemCount: 3, + itemCount: detail!.providers.length, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - final provider = detail!.providers[0]; + final provider = detail!.providers[index]; return Padding( padding: const EdgeInsetsDirectional.symmetric( vertical: 10, diff --git a/lib/tools/history.dart b/lib/tools/history.dart new file mode 100644 index 0000000..be67806 --- /dev/null +++ b/lib/tools/history.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:gymlink_module_web/interfaces/items.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +Future> getHistory() async { + final prefs = await SharedPreferences.getInstance(); + String historyString = prefs.getString('history') ?? "[]"; + List history = []; + for (var historyItem in jsonDecode(historyString) as List) { + history.add(GymHistoryItem.fromJson(historyItem)); + } + history.sort((a, b) => b.id.compareTo(a.id)); + history = history.reversed.toList(); + return history; +} + +Future addToHistory(GymHistoryItem item) async { + final prefs = await SharedPreferences.getInstance(); + String historyString = prefs.getString('history') ?? "[]"; + List history = []; + for (var historyItem in jsonDecode(historyString) as List) { + history.add(GymHistoryItem.fromJson(historyItem)); + } + item.id = Random().nextInt(100000).toString(); + String detailHistoryString = prefs.getString('detail_history') ?? "[]"; + List detailHistory = []; + for (var historyItem in jsonDecode(detailHistoryString) as List) { + detailHistory.add(GymHistoryItemDetail.fromJson(historyItem)); + } + var json = { + "id": item.id, + "date": DateTime.now() + .toLocal() + .toLocal() + .toString() + .split(' ')[0] + .replaceAll('-', '.') + .split('.') + .reversed + .join('.'), + "sum": Random().nextInt(100000).toString(), + "pay_url": [null, "https://example.org"][Random().nextInt(2)], + "receiver": "Иванов Иван Иванович ${Random().nextInt(100000).toString()}", + "email": "a${Random().nextInt(100000).toString()}@a.ru", + "address": + "г. ${['Москва', 'Петербург', 'Новгород'][Random().nextInt(3)]}, ул. ${[ + 'Пушкина', + 'Ленина', + 'Лермонтова' + ][Random().nextInt(3)]}, д. ${Random().nextInt(100).toString()}", + "providers": [ + { + "id": Random().nextInt(100000).toString(), + "name": "Поставщик ${Random().nextInt(100000).toString()}", + "status": ["Доставлен", "Доставляется", "Ожидает"][Random().nextInt(3)], + "items": [ + { + "photo": "url${Random().nextInt(100000).toString()}", + "id": Random().nextInt(100000).toString(), + "count": Random().nextInt(100), + "price": Random().nextInt(100000).toString() + }, + { + "photo": "url${Random().nextInt(100000).toString()}", + "id": Random().nextInt(100000).toString(), + "count": Random().nextInt(100), + "price": Random().nextInt(100000).toString() + } + ] + }, + { + "id": Random().nextInt(100000).toString(), + "name": "Поставщик ${Random().nextInt(100000).toString()}", + "status": ["Доставлен", "Доставляется", "Ожидает"][Random().nextInt(3)], + "items": [ + { + "photo": "url${Random().nextInt(100000).toString()}", + "id": Random().nextInt(100000).toString(), + "count": Random().nextInt(100), + "price": Random().nextInt(100000).toString() + } + ] + } + ] + }; + final detailHistoryItem = GymHistoryItemDetail.fromJson(json); + detailHistory.add(detailHistoryItem); + history.add(GymHistoryItem( + date: detailHistoryItem.date, + id: detailHistoryItem.id, + photo: 'product.png', + sum: detailHistoryItem.sum)); + prefs.setString('history', jsonEncode(history)); + prefs.setString('detail_history', jsonEncode(detailHistory)); +} + +Future getHistoryDetail(String id) async { + final prefs = await SharedPreferences.getInstance(); + String historyString = prefs.getString('detail_history') ?? "[]"; + for (var historyItem in jsonDecode(historyString) as List) { + if (GymHistoryItemDetail.fromJson(historyItem).id == id) { + return GymHistoryItemDetail.fromJson(historyItem); + } + } + return null; +}