Compare commits
65 Commits
26f822e83a
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f182c80210 | |||
| cefabd1c70 | |||
| f1055d40a4 | |||
| cfe0184e3a | |||
| faa52dcaa2 | |||
| 946d2ada41 | |||
| 7cb92a7b83 | |||
| dd59a605ad | |||
| 34c0ea5fa1 | |||
| 893b925a04 | |||
| bdcd4507c2 | |||
| fb5538ab79 | |||
| 0a22b5c051 | |||
| 73fe273c75 | |||
| f5e1407281 | |||
| 27da063c34 | |||
| c0c3ef2ca0 | |||
| 9335e8e694 | |||
| 04ee6d1699 | |||
| 65c8f56e20 | |||
| 0170505376 | |||
| 15105a7f33 | |||
| 1e5b235a6c | |||
| eaa8b138a4 | |||
| db39169907 | |||
| c8965dab4e | |||
| 97664fdb5a | |||
| cfa6ef9a67 | |||
| 7335c55703 | |||
| 1eeff4209e | |||
| 3b593ad733 | |||
| 0438a6feec | |||
| d6f64465b3 | |||
| e57c7dc0ea | |||
| e7073cec67 | |||
| 80da7e9008 | |||
| 7907dcf6c2 | |||
| 46ba11cd57 | |||
| d8e68f9b34 | |||
| e95fb08e31 | |||
| 28db4ce298 | |||
| 30fdc0a144 | |||
| 9ac9813244 | |||
| 986a9d9bd5 | |||
| c54176212a | |||
| e52357edf5 | |||
| 464f51238f | |||
| baf85776e9 | |||
| e177c81f8b | |||
| 51b0cbe89b | |||
| 9f720bdf75 | |||
| 2c2221f7f1 | |||
| 78e468cc29 | |||
| 6c4c2c4acd | |||
| 75bfc7ea6b | |||
| 57ff8a59e8 | |||
| b4092837d2 | |||
| 0c11883b48 | |||
| 4853f61da2 | |||
| 9d92dfd145 | |||
| 4b16b74d15 | |||
| fecc388e1c | |||
| e4628e977f | |||
| 4bbe7fbc0b | |||
| 5c3da0964a |
@@ -23,7 +23,7 @@ if (flutterVersionName == null) {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.example.flutter_application_1"
|
||||
namespace "com.example.gym_app"
|
||||
compileSdk flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
@@ -42,11 +42,11 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.flutter_application_1"
|
||||
applicationId "com.example.gym_app"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:label="flutter_application_1"
|
||||
android:label="Example Gym App"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
@@ -10,6 +11,7 @@
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.flutter_application_1
|
||||
package com.example.gym_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
14
assets/icon.svg
Normal file
14
assets/icon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1930_2195)">
|
||||
<path d="M13.2378 0.536444C13.2378 0.832951 13.5486 1.07336 13.9311 1.07336C14.9923 1.07336 17.0092 1.07336 18.0698 1.07336C18.4523 1.07336 18.7624 0.83289 18.7624 0.535822C18.7624 0.239315 18.4522 -3.74317e-05 18.0698 -3.74317e-05C17.0099 -3.74317e-05 14.9928 -3.74317e-05 13.9305 -3.74317e-05C13.5486 -3.74317e-05 13.2378 0.239938 13.2378 0.536444Z" fill="black"/>
|
||||
<path d="M13.2378 31.4624C13.2378 31.7589 13.5486 32 13.9311 32C14.9935 32 17.0074 32 18.0685 31.9994C18.451 31.9994 18.7612 31.7589 18.7625 31.4624C18.7625 31.1658 18.4523 30.926 18.0698 30.926C17.0093 30.926 14.9929 30.926 13.9305 30.9254C13.5486 30.9254 13.2378 31.1658 13.2378 31.4624Z" fill="black"/>
|
||||
<path d="M6.96338 24.3076C6.96338 25.2968 7.89411 26.0993 9.04154 26.0993C12.5199 26.0993 19.4814 26.0987 22.9585 26.0981C24.1059 26.0981 25.0366 25.2968 25.0366 24.3075C25.0366 23.3182 24.1053 22.5157 22.9585 22.5157C21.5944 22.5157 19.6919 22.5157 17.6761 22.5157C17.6761 21.4829 17.6761 12.2383 17.6761 9.4828C19.6925 9.4828 21.5944 9.4828 22.9591 9.4828C24.1059 9.4828 25.0366 8.68032 25.0366 7.69097C25.0366 6.70174 24.1053 5.90038 22.9585 5.90038C19.4801 5.90038 12.5186 5.90038 9.04154 5.90038C7.89354 5.90038 6.96338 6.7018 6.96338 7.69097C6.96338 8.68026 7.8929 9.4828 9.04154 9.4828C10.4056 9.4828 12.3075 9.4828 14.3238 9.4828C14.3238 12.2393 14.3238 21.4829 14.3238 22.5157C12.3075 22.5157 10.4055 22.5157 9.0409 22.5157C7.89296 22.5157 6.96338 23.3177 6.96338 24.3076Z" fill="black"/>
|
||||
<path d="M9.73926 3.39964C9.73926 3.45817 9.73926 3.51601 9.73926 3.57504C9.73926 4.32036 10.358 4.92535 11.1235 4.92535C13.5327 4.92535 18.4675 4.92535 20.8766 4.92535C21.6415 4.92535 22.2621 4.32042 22.2621 3.5746C22.2621 3.51607 22.2621 3.45699 22.2621 3.39914C22.2621 2.65332 21.6434 2.04839 20.8778 2.04839C18.4693 2.04839 13.5339 2.04839 11.1246 2.04889C10.3592 2.04827 9.73926 2.65382 9.73926 3.39964Z" fill="black"/>
|
||||
<path d="M9.73926 28.424C9.73926 28.4824 9.73926 28.5415 9.73926 28.6C9.73926 29.3458 10.358 29.9503 11.1235 29.9503C13.5327 29.9503 18.4675 29.9503 20.8766 29.9503C21.6422 29.9503 22.2621 29.3458 22.2621 28.6C22.2621 28.5415 22.2621 28.4824 22.2621 28.4246C22.2621 27.6788 21.6434 27.0744 20.8778 27.0744C18.4693 27.0744 13.5339 27.0744 11.1246 27.0744C10.3592 27.0737 9.73926 27.6782 9.73926 28.424Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1930_2195">
|
||||
<rect width="32" height="32" fill="white" transform="matrix(0 -1 1 0 0 32)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class GymLinkAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
const GymLinkAppBar({super.key});
|
||||
@@ -6,17 +7,27 @@ class GymLinkAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
shadowColor: null,
|
||||
automaticallyImplyLeading: false,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 4,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Image.asset('logo.png', width: 24, height: 24),
|
||||
child: SvgPicture.asset(
|
||||
'assets/icon.svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
semanticsLabel: 'GymLink Logo',
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/pages/detail.dart';
|
||||
import 'package:gymlink_module_web/tools/routes.dart';
|
||||
|
||||
class BasketItemCard extends StatelessWidget {
|
||||
final String name;
|
||||
final String price;
|
||||
final String id;
|
||||
final Image image;
|
||||
final VoidCallback onTap;
|
||||
final Widget image;
|
||||
final String quantity;
|
||||
final VoidCallback onTapPlus;
|
||||
final VoidCallback onTapMinus;
|
||||
|
||||
const BasketItemCard({
|
||||
super.key,
|
||||
@@ -13,7 +17,9 @@ class BasketItemCard extends StatelessWidget {
|
||||
required this.price,
|
||||
required this.id,
|
||||
required this.image,
|
||||
required this.onTap,
|
||||
required this.onTapPlus,
|
||||
required this.quantity,
|
||||
required this.onTapMinus,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -28,40 +34,58 @@ class BasketItemCard extends StatelessWidget {
|
||||
minWidth: 400,
|
||||
maxWidth: 600,
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
color: const Color(0xFFF2F3F9),
|
||||
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: [
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text('\$$price'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onTap,
|
||||
icon: const Icon(Icons.close),
|
||||
)
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
CustomPageRoute(builder: (context) => DetailPage(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.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
image,
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text('$price руб.'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: onTapMinus,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(quantity),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: onTapPlus,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/pages/main.dart';
|
||||
import 'package:gymlink_module_web/tools/routes.dart';
|
||||
|
||||
class GymLinkHeader extends StatelessWidget {
|
||||
final String title;
|
||||
const GymLinkHeader({super.key, required this.title});
|
||||
final bool toMain;
|
||||
const GymLinkHeader({super.key, required this.title, this.toMain = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -13,7 +16,13 @@ class GymLinkHeader extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
onPressed: () => toMain
|
||||
? Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
CustomPageRoute(
|
||||
builder: (context) => const MainPage()),
|
||||
(route) => route.isFirst)
|
||||
: Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back)),
|
||||
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
||||
],
|
||||
|
||||
83
lib/components/history_item_card.dart
Normal file
83
lib/components/history_item_card.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
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 }
|
||||
|
||||
Map<OrderStatus, String> orderStatusMap = {
|
||||
OrderStatus.created: 'Создан',
|
||||
OrderStatus.inProgress: 'В обработке',
|
||||
OrderStatus.completed: 'Завершен',
|
||||
OrderStatus.canceled: 'Отменен',
|
||||
};
|
||||
|
||||
class HistoryItemCard extends StatelessWidget {
|
||||
final String id;
|
||||
final String cost;
|
||||
final String date;
|
||||
final Widget image;
|
||||
|
||||
const HistoryItemCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.cost,
|
||||
required this.date,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.symmetric(horizontal: 10, vertical: 10),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 100,
|
||||
maxHeight: 200,
|
||||
minWidth: 600,
|
||||
maxWidth: 800,
|
||||
),
|
||||
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 руб.**'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProductCard extends StatelessWidget {
|
||||
final Image imagePath;
|
||||
final Widget imagePath;
|
||||
final String name;
|
||||
final String price;
|
||||
final VoidCallback onTap;
|
||||
@@ -14,15 +14,26 @@ class ProductCard extends StatelessWidget {
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
double getCardHeight({required BuildContext context}) {
|
||||
if (MediaQuery.of(context).size.width > 400) {
|
||||
return 300;
|
||||
}
|
||||
return 160;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 200),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 160,
|
||||
maxHeight: getCardHeight(context: context),
|
||||
minWidth: 180,
|
||||
maxWidth: 250),
|
||||
child: Card(
|
||||
elevation: 3,
|
||||
color: const Color(0xFFF2F3F9),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
@@ -32,9 +43,16 @@ class ProductCard extends StatelessWidget {
|
||||
children: [
|
||||
imagePath,
|
||||
const SizedBox(height: 16),
|
||||
Text(name, style: Theme.of(context).textTheme.titleLarge),
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text('\$$price', style: Theme.of(context).textTheme.titleSmall),
|
||||
Text(
|
||||
'$price руб.',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
63
lib/components/order_confirm_item_card.dart
Normal file
63
lib/components/order_confirm_item_card.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
class OrderConfirmItemCard extends StatelessWidget {
|
||||
final String name;
|
||||
final int count;
|
||||
final double price;
|
||||
final Widget image;
|
||||
|
||||
const OrderConfirmItemCard({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.count,
|
||||
required this.price,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
@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: [
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
'${price.toStringAsFixed(2)} руб. x $count = ${(price * count).toStringAsFixed(2)} руб.'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
MarkdownBody(data: '# X$count')
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
58
lib/components/order_detail_item_card.dart
Normal file
58
lib/components/order_detail_item_card.dart
Normal file
@@ -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')
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
325
lib/interfaces/items.dart
Normal file
325
lib/interfaces/items.dart
Normal file
@@ -0,0 +1,325 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ItemsDataResponse {
|
||||
final List<GymItem> rows;
|
||||
|
||||
ItemsDataResponse({
|
||||
required this.rows,
|
||||
});
|
||||
|
||||
factory ItemsDataResponse.fromRawJson(String str) =>
|
||||
ItemsDataResponse.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory ItemsDataResponse.fromJson(Map<String, dynamic> json) =>
|
||||
ItemsDataResponse(
|
||||
rows: List<GymItem>.from(json["rows"].map((x) => GymItem.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"rows": List<dynamic>.from(rows.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class GymItem {
|
||||
final String id;
|
||||
final String externalId;
|
||||
final String title;
|
||||
final String description;
|
||||
final int count;
|
||||
final double price;
|
||||
final String categoryId;
|
||||
final List<GymImage> images;
|
||||
final String supplierId;
|
||||
final String supplierName;
|
||||
int localCount = 0;
|
||||
|
||||
GymItem({
|
||||
required this.id,
|
||||
required this.externalId,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.count,
|
||||
required this.price,
|
||||
required this.categoryId,
|
||||
required this.images,
|
||||
required this.supplierId,
|
||||
required this.supplierName,
|
||||
});
|
||||
|
||||
factory GymItem.fromRawJson(String str) => GymItem.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymItem.fromJson(Map<String, dynamic> json) {
|
||||
return GymItem(
|
||||
id: json["id"],
|
||||
externalId: json["ExternalId"],
|
||||
title: json["title"],
|
||||
description: json["description"],
|
||||
count: json["count"],
|
||||
price: json["price"] / 100,
|
||||
categoryId: json["categoryId"],
|
||||
images:
|
||||
List<GymImage>.from(json["images"].map((x) => GymImage.fromJson(x))),
|
||||
supplierId: json["supplier"] == null ? '' : json["supplier"]["id"] ?? '',
|
||||
supplierName: json["supplier"] == null
|
||||
? ''
|
||||
: json["supplier"]["title"] ?? "Поставщик",
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"ExternalId": externalId,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"count": count,
|
||||
"price": price,
|
||||
"categoryId": categoryId,
|
||||
"images": List<dynamic>.from(images.map((x) => x.toJson())),
|
||||
"supplier":
|
||||
supplierId == '' ? null : {"id": supplierId, "title": supplierName},
|
||||
};
|
||||
}
|
||||
|
||||
class GymImage {
|
||||
final String id;
|
||||
final dynamic deletedAt;
|
||||
final String url;
|
||||
|
||||
GymImage({
|
||||
required this.id,
|
||||
required this.deletedAt,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
factory GymImage.fromRawJson(String str) =>
|
||||
GymImage.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymImage.fromJson(Map<String, dynamic> json) => GymImage(
|
||||
id: json["id"],
|
||||
deletedAt: json["deletedAt"],
|
||||
url: json["url"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"deletedAt": deletedAt,
|
||||
"url": url,
|
||||
};
|
||||
}
|
||||
|
||||
class CategoryDataResponse {
|
||||
final List<GymCategory> rows;
|
||||
|
||||
CategoryDataResponse({
|
||||
required this.rows,
|
||||
});
|
||||
|
||||
factory CategoryDataResponse.fromRawJson(String str) =>
|
||||
CategoryDataResponse.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory CategoryDataResponse.fromJson(Map<String, dynamic> json) =>
|
||||
CategoryDataResponse(
|
||||
rows: List<GymCategory>.from(
|
||||
json["rows"].map((x) => GymCategory.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"rows": List<dynamic>.from(rows.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class GymCategory {
|
||||
final String id;
|
||||
final String name;
|
||||
|
||||
GymCategory({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory GymCategory.fromRawJson(String str) =>
|
||||
GymCategory.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymCategory.fromJson(Map<String, dynamic> json) => GymCategory(
|
||||
id: json["id"],
|
||||
name: json["name"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"name": name,
|
||||
};
|
||||
}
|
||||
|
||||
class GymHistoryItem {
|
||||
String id;
|
||||
final String date;
|
||||
final String sum;
|
||||
final String photo;
|
||||
final String timestamp;
|
||||
|
||||
GymHistoryItem(
|
||||
{required this.id,
|
||||
required this.date,
|
||||
required this.sum,
|
||||
required this.photo,
|
||||
required this.timestamp});
|
||||
|
||||
factory GymHistoryItem.fromRawJson(String str) =>
|
||||
GymHistoryItem.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymHistoryItem.fromJson(Map<String, dynamic> json) => GymHistoryItem(
|
||||
id: json["id"],
|
||||
date: json["date"],
|
||||
sum: json["sum"],
|
||||
photo: json["photo"],
|
||||
timestamp: json["timestamp"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"date": date,
|
||||
"sum": sum,
|
||||
"timestamp": timestamp,
|
||||
"photo": photo,
|
||||
};
|
||||
}
|
||||
|
||||
class GymHistoryItemDetail {
|
||||
String id;
|
||||
final String date;
|
||||
final String sum;
|
||||
String? payUrl;
|
||||
final String receiver;
|
||||
final String email;
|
||||
final String timestamp;
|
||||
final String address;
|
||||
final List<GymHistoryItemDetailProvider> providers;
|
||||
|
||||
GymHistoryItemDetail(
|
||||
{required this.id,
|
||||
required this.date,
|
||||
required this.sum,
|
||||
this.payUrl,
|
||||
required this.providers,
|
||||
required this.receiver,
|
||||
required this.email,
|
||||
required this.address,
|
||||
required this.timestamp});
|
||||
|
||||
factory GymHistoryItemDetail.fromRawJson(String str) =>
|
||||
GymHistoryItemDetail.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymHistoryItemDetail.fromJson(Map<String, dynamic> json) =>
|
||||
GymHistoryItemDetail(
|
||||
id: json["id"],
|
||||
date: json["date"],
|
||||
sum: json["sum"],
|
||||
receiver: json["receiver"],
|
||||
email: json["email"],
|
||||
address: json["address"],
|
||||
payUrl: json["pay_url"] as String?,
|
||||
providers: List<GymHistoryItemDetailProvider>.from(json["providers"]
|
||||
.map((x) => GymHistoryItemDetailProvider.fromJson(x))),
|
||||
timestamp: json["timestamp"]);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"date": date,
|
||||
"sum": sum,
|
||||
"pay_url": payUrl,
|
||||
"providers": List<dynamic>.from(providers.map((x) => x.toJson())),
|
||||
"receiver": receiver,
|
||||
"email": email,
|
||||
"timestamp": timestamp,
|
||||
"address": address,
|
||||
};
|
||||
}
|
||||
|
||||
class GymHistoryItemDetailProvider {
|
||||
final String id;
|
||||
final String name;
|
||||
String status;
|
||||
final List<GymHistoryItemDetailItem> 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<String, dynamic> json) {
|
||||
return GymHistoryItemDetailProvider(
|
||||
id: json["id"],
|
||||
name: json["name"],
|
||||
status: json["status"],
|
||||
items: List<GymHistoryItemDetailItem>.from(
|
||||
json["items"].map((x) => GymHistoryItemDetailItem.fromJson(x))),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"status": status,
|
||||
"items": List<dynamic>.from(items.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class GymHistoryItemDetailItem {
|
||||
final String photo;
|
||||
final String id;
|
||||
final int count;
|
||||
final String name;
|
||||
final String price;
|
||||
|
||||
GymHistoryItemDetailItem({
|
||||
required this.photo,
|
||||
required this.id,
|
||||
required this.count,
|
||||
required this.price,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory GymHistoryItemDetailItem.fromRawJson(String str) =>
|
||||
GymHistoryItemDetailItem.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory GymHistoryItemDetailItem.fromJson(Map<String, dynamic> json) =>
|
||||
GymHistoryItemDetailItem(
|
||||
photo: json["photo"],
|
||||
id: json["id"],
|
||||
count: json["count"],
|
||||
price: json["price"],
|
||||
name: json["name"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"photo": photo,
|
||||
"id": id,
|
||||
"count": count,
|
||||
"price": price,
|
||||
"name": name,
|
||||
};
|
||||
}
|
||||
256
lib/main.dart
256
lib/main.dart
@@ -1,263 +1,25 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop' as js;
|
||||
import 'dart:js_interop_unsafe' as js_util;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/components/app_bar.dart';
|
||||
import 'package:gymlink_module_web/components/item_card.dart';
|
||||
import 'package:gymlink_module_web/pages/basket.dart';
|
||||
import 'package:gymlink_module_web/pages/detail.dart';
|
||||
import 'package:gymlink_module_web/pages/order_history.dart';
|
||||
import 'package:gymlink_module_web/theme.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:gymlink_module_web/providers/main.dart';
|
||||
import 'package:gymlink_module_web/states/web.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
runApp(const MyAppWithProvider());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
State<MyApp> createState() => MyAppStateWeb();
|
||||
}
|
||||
|
||||
const List<Map<String, String>> testData = [
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "120",
|
||||
"details": "Test details",
|
||||
"id": "34fa3126-bfaf-5dec-8f4a-b246c097ef73"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "150",
|
||||
"details":
|
||||
"that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his",
|
||||
"id": "34a26e82-7656-5e98-a44a-c2d01d0b1ad1123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "250",
|
||||
"details": "Test details",
|
||||
"id": "4fb204b7-3f9e-52a2-bed1-415c00a31a37123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "300",
|
||||
"details": "Test details",
|
||||
"id": "09b2f5bb-683e-5c39-ae89-b8e152fa8bcf123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "100",
|
||||
"details": "Test details",
|
||||
"id": "cd1b6817-db94-5394-be1d-af88af79749f123"
|
||||
}
|
||||
];
|
||||
|
||||
@js.JSExport()
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final _streamController = StreamController<void>.broadcast();
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final export = js.createJSInteropWrapper(this);
|
||||
js.globalContext['_appState'] = export;
|
||||
js.globalContext.callMethod('_stateSet'.toJS);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamController.close();
|
||||
super.dispose();
|
||||
}
|
||||
class MyAppWithProvider extends StatelessWidget {
|
||||
const MyAppWithProvider({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: myTheme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: MainPage(isLoading: _isLoading),
|
||||
);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void onTokenReceived(String token) {
|
||||
if (token == 'token123') {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MainPage extends StatefulWidget {
|
||||
final bool isLoading;
|
||||
|
||||
const MainPage({
|
||||
super.key,
|
||||
required this.isLoading,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MainPage> createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> {
|
||||
Future<void> _goToPage() async {
|
||||
final Uri url = Uri.parse('https://google.com');
|
||||
if (!await launchUrl(url, webOnlyWindowName: '_blank')) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: widget.isLoading ? null : const GymLinkAppBar(),
|
||||
body: widget.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ElevatedButton(
|
||||
onPressed: _goToPage,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 0,
|
||||
),
|
||||
minimumSize:
|
||||
const Size(50, kMinInteractiveDimension),
|
||||
backgroundColor:
|
||||
Theme.of(context).primaryColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 2,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => const BasketPage(),
|
||||
));
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
minimumSize: const Size(50, kMinInteractiveDimension),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.black,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.shopping_basket,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => const HistoryPage(),
|
||||
));
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
minimumSize: const Size(50, kMinInteractiveDimension),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.black,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.history,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount:
|
||||
(MediaQuery.sizeOf(context).width ~/ 250).floor(),
|
||||
),
|
||||
itemCount: testData.length,
|
||||
itemBuilder: (context, index) {
|
||||
final product = testData[index];
|
||||
return ProductCard(
|
||||
imagePath: Image.asset(
|
||||
product['image']!,
|
||||
width: 100,
|
||||
),
|
||||
name: product['name']!,
|
||||
price: product['price']!,
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DetailPage(
|
||||
name: product['name']!,
|
||||
description: product['details']!,
|
||||
price: product['price']!,
|
||||
id: product['id']!,
|
||||
image: Image.asset(product['image']!, width: 300),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => GymLinkProvider(), child: const MyApp());
|
||||
}
|
||||
}
|
||||
|
||||
13
lib/main_mobile.dart
Normal file
13
lib/main_mobile.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/states/mobile.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => MyAppStateMobile();
|
||||
}
|
||||
280
lib/mobile_example.dart
Normal file
280
lib/mobile_example.dart
Normal file
@@ -0,0 +1,280 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gymlink_module_web/main_mobile.dart';
|
||||
import 'package:gymlink_module_web/providers/main.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyExampleApp());
|
||||
}
|
||||
|
||||
Future<String> getToken(String token, String clientId) async {
|
||||
var url = Uri.https('gymlink.freemyip.com', 'api/auth/authorize_client');
|
||||
try {
|
||||
var response = await http.post(url,
|
||||
body: {'GymKey': token, 'id': clientId}); // Just testing token
|
||||
var decodedBody = jsonDecode(response.body) as Map;
|
||||
if (decodedBody['payload'] == null) {
|
||||
return '';
|
||||
}
|
||||
return decodedBody['payload']['token'];
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
class MyExampleApp extends StatelessWidget {
|
||||
const MyExampleApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
return MaterialApp(
|
||||
title: 'GymLink Example App',
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const ExampleMainPage(),
|
||||
theme: ThemeData.light(useMaterial3: true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExamplePage extends StatefulWidget {
|
||||
const ExamplePage({super.key});
|
||||
|
||||
@override
|
||||
State<ExamplePage> createState() => _ExamplePageState();
|
||||
}
|
||||
|
||||
Widget getDrawer(BuildContext context) => Drawer(
|
||||
child: Column(
|
||||
children: [
|
||||
const DrawerHeader(child: Text('Drawer Header')),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.home),
|
||||
title: const Text('Home'),
|
||||
onTap: () {
|
||||
Future.microtask(() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('token');
|
||||
prefs.remove('history');
|
||||
prefs.remove('cart');
|
||||
prefs.remove('detail_history');
|
||||
});
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ExampleMainPage(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.sell),
|
||||
title: const Text('Club 2'),
|
||||
onTap: () {
|
||||
Future.microtask(() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('token');
|
||||
prefs.remove('history');
|
||||
prefs.remove('cart');
|
||||
prefs.remove('detail_history');
|
||||
});
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider(
|
||||
create: (_) => GymLinkProvider(),
|
||||
child: Consumer<GymLinkProvider>(
|
||||
builder: (_, value, __) => const ExampleClub2Page(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.search),
|
||||
title: const Text('Example page'),
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) => const ExampleSecondPage(),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
class ExampleMainPage extends StatelessWidget {
|
||||
const ExampleMainPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => GymLinkProvider(),
|
||||
child: Consumer<GymLinkProvider>(
|
||||
builder: (_, value, __) => const ExamplePage(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class _ExamplePageState extends State<ExamplePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Future.microtask(
|
||||
// () => context.read<GymLinkProvider>().onTokenReceived('token123'));
|
||||
Future.microtask(() => context
|
||||
.read<GymLinkProvider>()
|
||||
.setTheme(ThemeData.light(useMaterial3: true)));
|
||||
|
||||
Future.microtask(() => context.read<GymLinkProvider>().setOnError(() {
|
||||
const snackBar = SnackBar(
|
||||
content: Text('Ошибка подключения'),
|
||||
duration: Duration(seconds: 3), // Длительность отображения Snackbar
|
||||
behavior: SnackBarBehavior
|
||||
.fixed, // Поведение Snackbar (fixed или floating)
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Future.delayed(const Duration(seconds: 3))
|
||||
.then((value) => _setToken());
|
||||
}));
|
||||
|
||||
Future.microtask(() async {
|
||||
await _setToken();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setToken() async {
|
||||
final token = await getToken('eeb42dcb-8e5b-4f21-825a-3fc7ada43445', '123');
|
||||
if (token != '') {
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
} else {
|
||||
context.read<GymLinkProvider>().onError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('GymLink Example App Gym 1'),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
drawer: getDrawer(context),
|
||||
body: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.colorize),
|
||||
onPressed: () {
|
||||
context.read<GymLinkProvider>().changeTheme(
|
||||
Random().nextInt(0xffffff + 1),
|
||||
blackTheme: Random().nextBool());
|
||||
},
|
||||
),
|
||||
const Expanded(
|
||||
child: MyApp(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const Text('Bottom text')
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleClub2Page extends StatefulWidget {
|
||||
const ExampleClub2Page({super.key});
|
||||
|
||||
@override
|
||||
State<ExampleClub2Page> createState() => _ExampleClub2PageState();
|
||||
}
|
||||
|
||||
class _ExampleClub2PageState extends State<ExampleClub2Page> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Future.microtask(
|
||||
// () => context.read<GymLinkProvider>().onTokenReceived('token123'));
|
||||
Future.microtask(() => context
|
||||
.read<GymLinkProvider>()
|
||||
.setTheme(ThemeData.light(useMaterial3: true)));
|
||||
|
||||
Future.microtask(() => context.read<GymLinkProvider>().setOnError(() {
|
||||
const snackBar = SnackBar(
|
||||
content: Text('Ошибка подключения'),
|
||||
duration: Duration(seconds: 3), // Длительность отображения Snackbar
|
||||
behavior: SnackBarBehavior
|
||||
.fixed, // Поведение Snackbar (fixed или floating)
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
Future.delayed(const Duration(seconds: 3))
|
||||
.then((value) => _setToken());
|
||||
}));
|
||||
|
||||
Future.microtask(() async {
|
||||
await _setToken();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setToken() async {
|
||||
final token = await getToken('a8622a61-3142-487e-8db8-b6aebd4f04aa', '123');
|
||||
context.read<GymLinkProvider>().changeTheme(0xFFAABCAB);
|
||||
if (token != '') {
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
} else {
|
||||
context.read<GymLinkProvider>().onError();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('GymLink Example App Gym2'),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
drawer: getDrawer(context),
|
||||
body: const Column(
|
||||
children: [
|
||||
Text('test'),
|
||||
Expanded(
|
||||
child: MyApp(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Bottom text')
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleSecondPage extends StatelessWidget {
|
||||
const ExampleSecondPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('GymLink Example App'),
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
body: Center(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('token');
|
||||
prefs.remove('history');
|
||||
prefs.remove('cart');
|
||||
prefs.remove('detail_history');
|
||||
},
|
||||
child: const Text('Clear')),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/components/app_bar.dart';
|
||||
import 'package:gymlink_module_web/components/basket_item_card.dart';
|
||||
import 'package:gymlink_module_web/components/heading.dart';
|
||||
import 'package:gymlink_module_web/interfaces/items.dart';
|
||||
import 'package:gymlink_module_web/pages/main.dart';
|
||||
import 'package:gymlink_module_web/pages/order_confirmation.dart';
|
||||
import 'package:gymlink_module_web/providers/cart.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';
|
||||
import 'package:gymlink_module_web/tools/text.dart';
|
||||
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
List<Map<String, dynamic>> cart = [
|
||||
{
|
||||
@@ -50,31 +59,176 @@ class BasketPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BasketPageState extends State<BasketPage> {
|
||||
List<Map<String, dynamic>> cartItems = [];
|
||||
int totalPrice = 0;
|
||||
List<GymItem> cartItems = [];
|
||||
double totalPrice = 0;
|
||||
List<GymItem> gymCart = [];
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCart().then((value) {
|
||||
setState(() {
|
||||
cartItems = cart
|
||||
.where((element) => value.any((e) => e['id'] == element['id']))
|
||||
.toList();
|
||||
totalPrice =
|
||||
cartItems.fold(0, (sum, item) => sum + int.parse(item['price']));
|
||||
debugPrint(totalPrice.toString());
|
||||
});
|
||||
});
|
||||
Future.microtask(() => getCart().then((value) async {
|
||||
final itemIds =
|
||||
value.map((element) => element['id'] as String).toList();
|
||||
final items = await getItemsByIds(context, itemIds);
|
||||
setState(() {
|
||||
gymCart = items;
|
||||
cartItems = value.map((element) {
|
||||
final item = gymCart.firstWhere((e) => e.id == element['id']);
|
||||
item.localCount = element['count'] as int;
|
||||
return item;
|
||||
}).toList();
|
||||
totalPrice = cartItems.fold(
|
||||
0, (sum, item) => sum + item.price * item.localCount);
|
||||
_isLoading = false;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
void _updateCart() {
|
||||
Provider.of<CartProvider>(context, listen: false).updateCartLength();
|
||||
}
|
||||
|
||||
void removeItem(String id) async {
|
||||
final item = cartItems.firstWhere((element) => element.id == id);
|
||||
bool toDelete = false;
|
||||
setState(() {
|
||||
cartItems.removeWhere((element) => element['id'] == id);
|
||||
if (item.localCount > 1) {
|
||||
item.localCount--;
|
||||
cartItems[cartItems.indexOf(item)].localCount = item.localCount;
|
||||
} else {
|
||||
toDelete = true;
|
||||
}
|
||||
totalPrice =
|
||||
cartItems.fold(0, (sum, item) => sum + int.parse(item['price']));
|
||||
cartItems.fold(0, (sum, item) => sum + item.price * item.localCount);
|
||||
});
|
||||
await removeItemFromCart(id);
|
||||
if (toDelete) {
|
||||
await _deleteItemAlert(id, item.title);
|
||||
} else {
|
||||
await removeItemFromCart(id);
|
||||
}
|
||||
}
|
||||
|
||||
void addItem(String id) async {
|
||||
final item =
|
||||
cartItems.firstWhere((element) => element.id == id, orElse: () {
|
||||
final cartItem = gymCart.firstWhere((element) => element.id == id);
|
||||
cartItem.localCount = 0;
|
||||
return cartItem;
|
||||
});
|
||||
if (item.localCount + 1 > item.count) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
item.localCount++;
|
||||
cartItems[cartItems.indexOf(item)].localCount = item.localCount;
|
||||
totalPrice =
|
||||
cartItems.fold(0, (sum, item) => sum + item.price * item.localCount);
|
||||
});
|
||||
await addItemToCart(id);
|
||||
}
|
||||
|
||||
Future<void> _deleteItemAlert(String id, String name) async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Удаление из корзины'),
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: [
|
||||
Text('Вы действительно хотите убрать "$name" из корзины?'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Отмена'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Удалить'),
|
||||
onPressed: () {
|
||||
removeItemFromCart(id);
|
||||
setState(() {
|
||||
cartItems.removeWhere((element) => element.id == id);
|
||||
totalPrice = cartItems.fold(
|
||||
0, (sum, item) => sum + item.price * item.localCount);
|
||||
});
|
||||
if (mounted) {
|
||||
_updateCart();
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _clearCartAlert() async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Очистка корзины'),
|
||||
content: const SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: [
|
||||
Text('Вы действительно хотите очистить корзину?'),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Отмена'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Очистить'),
|
||||
onPressed: () {
|
||||
clearCart();
|
||||
setState(() {
|
||||
cartItems = [];
|
||||
});
|
||||
if (mounted) {
|
||||
_updateCart();
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRowOrCol(
|
||||
{required BuildContext context, required List<Widget> children}) {
|
||||
if (MediaQuery.of(context).size.width > 600) {
|
||||
return Row(children: children);
|
||||
}
|
||||
return Column(children: children);
|
||||
}
|
||||
|
||||
Widget _buildSpacer() {
|
||||
if (MediaQuery.of(context).size.width > 600) {
|
||||
return const Spacer();
|
||||
}
|
||||
return const SizedBox(height: 10);
|
||||
}
|
||||
|
||||
void _onLoad() async {
|
||||
await Future.delayed(const Duration(microseconds: 1000));
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -84,63 +238,24 @@ class _BasketPageState extends State<BasketPage> {
|
||||
body: Column(
|
||||
children: [
|
||||
const GymLinkHeader(title: "Корзина"),
|
||||
cartItems.isEmpty
|
||||
? Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Корзина пуста',
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Вернуться назад'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: cartItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = cartItems[index];
|
||||
return BasketItemCard(
|
||||
name: item['name'],
|
||||
price: item['price'],
|
||||
id: item['id'],
|
||||
image: Image.asset(
|
||||
item['image'],
|
||||
width: 50,
|
||||
),
|
||||
onTap: () => removeItem(item['id']),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 10, vertical: 10),
|
||||
_isLoading
|
||||
? const Expanded(
|
||||
child: Center(child: CircularProgressIndicator()))
|
||||
: cartItems.isEmpty
|
||||
? Expanded(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Итого: $totalPrice',
|
||||
),
|
||||
Text('Корзина пуста',
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
onPressed: () => Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
CustomPageRoute(
|
||||
builder: (_) => const MainPage()),
|
||||
(route) => route.isFirst),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
@@ -149,15 +264,102 @@ class _BasketPageState extends State<BasketPage> {
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Оформить заказ'),
|
||||
child: const Text('Вернуться назад'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 50),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Expanded(
|
||||
child: _buildRowOrCol(
|
||||
context: context,
|
||||
children: [
|
||||
Expanded(
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: _onLoad,
|
||||
child: ListView.builder(
|
||||
itemCount: cartItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = cartItems[index];
|
||||
return BasketItemCard(
|
||||
name: shortString(item.title),
|
||||
price: item.price.toStringAsFixed(2),
|
||||
id: item.id,
|
||||
image: FutureBuilder(
|
||||
future: precacheImage(
|
||||
NetworkImage(item.images[0].url),
|
||||
context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
return Image(
|
||||
image: NetworkImage(
|
||||
item.images[0].url),
|
||||
width: 50,
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
onTapPlus: () =>
|
||||
addItem(item.id.toString()),
|
||||
onTapMinus: () {
|
||||
removeItem(item.id.toString());
|
||||
},
|
||||
quantity: item.localCount.toString(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// _buildSpacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 10, vertical: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Итого: ${totalPrice.toStringAsFixed(2)} руб.',
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
CustomPageRoute(
|
||||
builder: (context) =>
|
||||
const OrderConfirmationPage(),
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Оформить заказ'),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: _clearCartAlert,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(50))),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Очистить корзину'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 50),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
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:gymlink_module_web/pages/basket.dart';
|
||||
import 'package:gymlink_module_web/providers/cart.dart';
|
||||
import 'package:gymlink_module_web/providers/main.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';
|
||||
import 'package:gymlink_module_web/tools/text.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
//TODO: Сделать получение инфы через объект
|
||||
class DetailPage extends StatefulWidget {
|
||||
final String name;
|
||||
final String description;
|
||||
final String price;
|
||||
final String id;
|
||||
final Image image;
|
||||
const DetailPage({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.price,
|
||||
required this.id,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -26,10 +31,13 @@ class DetailPage extends StatefulWidget {
|
||||
class _DetailPageState extends State<DetailPage> {
|
||||
bool isInCart = false;
|
||||
int quantity = 0;
|
||||
GymItem? item;
|
||||
String? categoryName;
|
||||
final CarouselController _carouselController = CarouselController();
|
||||
int _currentImage = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCart().then((value) {
|
||||
setState(() {
|
||||
isInCart = value.any((element) => element['id'] == widget.id);
|
||||
@@ -39,6 +47,51 @@ class _DetailPageState extends State<DetailPage> {
|
||||
}
|
||||
});
|
||||
});
|
||||
_getItem();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _getItem() async {
|
||||
final Uri url =
|
||||
Uri.https('gymlink.freemyip.com', 'api/product/get/${widget.id}');
|
||||
final response = await http.get(url, headers: {
|
||||
'Authorization': 'Bearer ${context.read<GymLinkProvider>().token}',
|
||||
});
|
||||
if (response.statusCode == 200) {
|
||||
final data =
|
||||
GymItem.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
|
||||
setState(() {
|
||||
item = data;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
for (var element in item!.images) {
|
||||
precacheImage(NetworkImage(element.url), context);
|
||||
}
|
||||
});
|
||||
if (mounted) {
|
||||
getCategories(context).then((value) {
|
||||
setState(() {
|
||||
categoryName = value
|
||||
.firstWhere(
|
||||
(element) => element.id == (item!.categoryId),
|
||||
orElse: () => GymCategory(id: item!.categoryId, name: ''),
|
||||
)
|
||||
.name;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildRowOrCol(
|
||||
{required List<Widget> children,
|
||||
required BuildContext context,
|
||||
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceAround,
|
||||
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center}) {
|
||||
return Column(
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
children: children);
|
||||
}
|
||||
|
||||
Widget _buildButton() {
|
||||
@@ -50,6 +103,9 @@ class _DetailPageState extends State<DetailPage> {
|
||||
isInCart = true;
|
||||
quantity = 1;
|
||||
});
|
||||
if (mounted) {
|
||||
context.read<CartProvider>().updateCartLength();
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
@@ -61,113 +117,245 @@ class _DetailPageState extends State<DetailPage> {
|
||||
child: const Text('Добавить в корзину'),
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
return Column(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () async {
|
||||
await removeItemFromCart(widget.id);
|
||||
setState(() {
|
||||
if (quantity > 1) {
|
||||
quantity--;
|
||||
} else {
|
||||
isInCart = false;
|
||||
quantity = 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
Text('$quantity'),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
await addItemToCart(widget.id);
|
||||
setState(() {
|
||||
quantity++;
|
||||
});
|
||||
},
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () async {
|
||||
await removeItemFromCart(widget.id);
|
||||
setState(() {
|
||||
if (quantity > 1) {
|
||||
quantity--;
|
||||
} else {
|
||||
isInCart = false;
|
||||
quantity = 0;
|
||||
}
|
||||
});
|
||||
if (mounted) {
|
||||
context.read<CartProvider>().updateCartLength();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text('$quantity'),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
if (item!.count > quantity) {
|
||||
await addItemToCart(widget.id);
|
||||
setState(() {
|
||||
quantity++;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pushReplacement(context,
|
||||
CustomPageRoute(builder: (context) => const BasketPage()));
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Открыть корзину'),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
double _getAspectRatio() {
|
||||
double width = MediaQuery.sizeOf(context).width;
|
||||
double height = MediaQuery.sizeOf(context).height;
|
||||
return max(width, height) / min(width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
body: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||
GymLinkHeader(title: '${widget.name} - ${widget.id}'),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: MediaQuery.sizeOf(context).height,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
widget.image,
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.fromSTEB(0, 30, 60, 60),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 340,
|
||||
maxWidth: 340,
|
||||
maxHeight: 600,
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
color: const Color(0xFFF2F3F9),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
body: item != null
|
||||
? Column(mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||
GymLinkHeader(title: shortString(item!.title, length: 20)),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
child: _buildRowOrCol(
|
||||
context: context,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
item!.images.length > 1
|
||||
? Column(children: [
|
||||
CarouselSlider.builder(
|
||||
itemCount: item!.images.length,
|
||||
itemBuilder: (context, index, realIdx) {
|
||||
return Center(
|
||||
child: Image.network(
|
||||
item!.images[index].url,
|
||||
width: min(
|
||||
550,
|
||||
MediaQuery.sizeOf(context)
|
||||
.width)),
|
||||
);
|
||||
},
|
||||
carouselController: _carouselController,
|
||||
options: CarouselOptions(
|
||||
enlargeCenterPage: true,
|
||||
height: min(
|
||||
MediaQuery.sizeOf(context).height -
|
||||
100,
|
||||
400),
|
||||
enableInfiniteScroll: false,
|
||||
onPageChanged: (index, reason) {
|
||||
setState(() {
|
||||
_currentImage = index;
|
||||
});
|
||||
}),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: item!.images
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
return GestureDetector(
|
||||
onTap: () => _carouselController
|
||||
.animateToPage(entry.key),
|
||||
child: Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 8.0, horizontal: 4.0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: (Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black)
|
||||
.withOpacity(
|
||||
_currentImage == entry.key
|
||||
? 0.9
|
||||
: 0.4)),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
])
|
||||
: Image.network(
|
||||
item!.images[0].url,
|
||||
height: 400,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
item!.title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(
|
||||
20, 15, 10, 15),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 100,
|
||||
),
|
||||
child: Text(
|
||||
widget.description,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Chip(
|
||||
label: Text(categoryName != null
|
||||
? (categoryName == ""
|
||||
? "Без категории"
|
||||
: categoryName!)
|
||||
: ''),
|
||||
backgroundColor: Colors.white,
|
||||
labelStyle:
|
||||
const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: const AlignmentDirectional(0, -1),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.fromSTEB(0, 60, 0, 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Стоимость ${widget.price}',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
Center(
|
||||
child: MarkdownBody(
|
||||
data: '### Остаток: _${item!.count}_',
|
||||
)),
|
||||
item!.description != ''
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.all(30),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 340,
|
||||
maxWidth: 340,
|
||||
maxHeight: 600,
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
color: Theme.of(context)
|
||||
.scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.all(
|
||||
15),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 100,
|
||||
),
|
||||
child: Text(
|
||||
item!.description,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Align(
|
||||
alignment: const AlignmentDirectional(0, -1),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(
|
||||
0, 30, 0, 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Стоимость ${item!.price.toStringAsFixed(2)}руб.',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
_buildButton()
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildButton()
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
])
|
||||
: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
409
lib/pages/main.dart
Normal file
409
lib/pages/main.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/components/app_bar.dart';
|
||||
import 'package:gymlink_module_web/components/item_card.dart';
|
||||
import 'package:gymlink_module_web/interfaces/items.dart';
|
||||
import 'package:gymlink_module_web/pages/basket.dart';
|
||||
import 'package:gymlink_module_web/pages/detail.dart';
|
||||
import 'package:gymlink_module_web/pages/order_history.dart';
|
||||
import 'package:gymlink_module_web/providers/cart.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';
|
||||
import 'package:gymlink_module_web/tools/text.dart';
|
||||
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
const List<Map<String, String>> testData = [
|
||||
{
|
||||
"name": "Протеин 2",
|
||||
"image": "product.png",
|
||||
"price": "120",
|
||||
"details": "Test details",
|
||||
"id": "34fa3126-bfaf-5dec-8f4a-b246c097ef73"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "150",
|
||||
"details":
|
||||
"that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his",
|
||||
"id": "34a26e82-7656-5e98-a44a-c2d01d0b1ad1123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "250",
|
||||
"details": "Test details",
|
||||
"id": "4fb204b7-3f9e-52a2-bed1-415c00a31a37123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "300",
|
||||
"details": "Test details",
|
||||
"id": "09b2f5bb-683e-5c39-ae89-b8e152fa8bcf123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "100",
|
||||
"details": "Test details",
|
||||
"id": "cd1b6817-db94-5394-be1d-af88af79749f123"
|
||||
}
|
||||
];
|
||||
|
||||
class MainPage extends StatefulWidget {
|
||||
const MainPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MainPage> createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> {
|
||||
String searchText = '';
|
||||
List<GymItem> filteredData = [];
|
||||
int cartLength = 0;
|
||||
int itemViewCount = 0;
|
||||
bool isLoading = false;
|
||||
bool isSearching = false;
|
||||
List<GymCategory> categories = [];
|
||||
GymCategory? selectedCategory;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final TextEditingController _searchField = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCart().then((value) {
|
||||
setState(() {
|
||||
cartLength = value.length;
|
||||
});
|
||||
});
|
||||
getCategories(context).then((value) => setState(() {
|
||||
categories = value;
|
||||
_onSearch();
|
||||
}));
|
||||
}
|
||||
|
||||
void _onLoad() async {
|
||||
if (itemViewCount < filteredData.length) {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
setState(() {
|
||||
itemViewCount = min(filteredData.length, itemViewCount + 5);
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _searchItems({String searchText = '', String categoryId = ''}) async {
|
||||
setState(() {
|
||||
isSearching = true;
|
||||
});
|
||||
final data =
|
||||
await getItems(context, searchText: searchText, categoryId: categoryId);
|
||||
setState(() {
|
||||
filteredData = data;
|
||||
itemViewCount = min(filteredData.length, 5);
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
for (var element in filteredData.sublist(0, itemViewCount)) {
|
||||
precacheImage(NetworkImage(element.images[0].url), context);
|
||||
}
|
||||
});
|
||||
setState(() {
|
||||
isSearching = false;
|
||||
});
|
||||
}
|
||||
|
||||
void _onSearch() {
|
||||
final categoryId = selectedCategory == null ? '' : selectedCategory!.id;
|
||||
setState(() {
|
||||
searchText = _searchField.text.trim().toLowerCase();
|
||||
});
|
||||
_searchItems(searchText: searchText, categoryId: categoryId);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cartL = context.watch<CartProvider>().cartLength;
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) {
|
||||
searchText = value.trim().toLowerCase();
|
||||
if (searchText == '') {
|
||||
_onSearch();
|
||||
}
|
||||
},
|
||||
controller: _searchField,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (_) => _onSearch(),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Поиск',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: ElevatedButton(
|
||||
onPressed: _onSearch,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 0,
|
||||
),
|
||||
minimumSize:
|
||||
const Size(50, kMinInteractiveDimension),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// getSpacer(context: context, flex: 2),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(CustomPageRoute(
|
||||
builder: (context) => const HistoryPage(),
|
||||
));
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
minimumSize: const Size(50, kMinInteractiveDimension),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.black,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.history,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
final category = categories[index];
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedCategory =
|
||||
selectedCategory == category ? null : category;
|
||||
});
|
||||
_onSearch();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 10),
|
||||
child: Chip(
|
||||
label: Text(category.name),
|
||||
backgroundColor: selectedCategory == category
|
||||
? Theme.of(context).primaryColor
|
||||
: Colors.white,
|
||||
labelStyle: TextStyle(
|
||||
color: selectedCategory == category
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: _onLoad,
|
||||
isLoading: isLoading,
|
||||
child: Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
filteredData.isEmpty &&
|
||||
(searchText != '' || selectedCategory != null) &&
|
||||
!isSearching
|
||||
? const Center(child: Text('Ничего не найдено'))
|
||||
: isSearching
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: GridView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: min(
|
||||
(MediaQuery.sizeOf(context).width ~/
|
||||
220)
|
||||
.toInt(),
|
||||
8),
|
||||
childAspectRatio: 0.8,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 20.0),
|
||||
itemCount: itemViewCount,
|
||||
itemBuilder: (context, index) {
|
||||
final product = filteredData[index];
|
||||
return ProductCard(
|
||||
imagePath: FutureBuilder(
|
||||
future: precacheImage(
|
||||
NetworkImage(product.images[0].url),
|
||||
context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
return Image(
|
||||
image: NetworkImage(
|
||||
product.images[0].url),
|
||||
width: 120,
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
name: shortString(product.title),
|
||||
price: product.price.toStringAsFixed(2),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
CustomPageRoute(
|
||||
builder: (context) => DetailPage(
|
||||
id: product.id,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
itemViewCount > 0 && !isSearching
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Center(
|
||||
child: itemViewCount < filteredData.length
|
||||
? !isLoading
|
||||
? ElevatedButton(
|
||||
onPressed: _onLoad,
|
||||
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.arrow_downward),
|
||||
Spacer()
|
||||
],
|
||||
))
|
||||
: const CircularProgressIndicator()
|
||||
: const Text(
|
||||
'Конец списка',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Color(0x88000000)),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: SizedBox(
|
||||
height: 80,
|
||||
width: 80,
|
||||
child: FittedBox(
|
||||
child: Stack(
|
||||
children: [
|
||||
FloatingActionButton(
|
||||
onPressed: () => Navigator.of(context).push(CustomPageRoute(
|
||||
builder: (context) => const BasketPage(),
|
||||
)),
|
||||
highlightElevation: 0,
|
||||
hoverColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
hoverElevation: 0,
|
||||
focusElevation: 0,
|
||||
splashColor: Colors.transparent,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
child: CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
child: const Icon(Icons.shopping_cart_outlined)),
|
||||
),
|
||||
cartL > 0
|
||||
? Positioned(
|
||||
right: -3,
|
||||
bottom: 0,
|
||||
child: Card(
|
||||
color: Colors.red,
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
child: Center(
|
||||
child: Text(
|
||||
cartL.toString(),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
|
||||
);
|
||||
}
|
||||
}
|
||||
330
lib/pages/order_confirmation.dart
Normal file
330
lib/pages/order_confirmation.dart
Normal file
@@ -0,0 +1,330 @@
|
||||
import 'dart:math';
|
||||
|
||||
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/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';
|
||||
import 'package:gymlink_module_web/tools/text.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
List<Map<String, dynamic>> cart = [
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "120",
|
||||
"details": "Test details",
|
||||
"id": "34fa3126-bfaf-5dec-8f4a-b246c097ef73"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "150",
|
||||
"details": "Test details",
|
||||
"id": "34a26e82-7656-5e98-a44a-c2d01d0b1ad1123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "250",
|
||||
"details": "Test details",
|
||||
"id": "4fb204b7-3f9e-52a2-bed1-415c00a31a37123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "300",
|
||||
"details": "Test details",
|
||||
"id": "09b2f5bb-683e-5c39-ae89-b8e152fa8bcf123"
|
||||
},
|
||||
{
|
||||
"name": "Протеин",
|
||||
"image": "product.png",
|
||||
"price": "100",
|
||||
"details": "Test details",
|
||||
"id": "cd1b6817-db94-5394-be1d-af88af79749f123"
|
||||
}
|
||||
];
|
||||
|
||||
class OrderConfirmationPage extends StatefulWidget {
|
||||
const OrderConfirmationPage({super.key});
|
||||
|
||||
@override
|
||||
State<OrderConfirmationPage> createState() => _OrderConfirmationPageState();
|
||||
}
|
||||
|
||||
class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
List<GymItem> cartItems = [];
|
||||
double totalPrice = 0;
|
||||
List<GymItem> gymCart = [];
|
||||
bool isAgree = false;
|
||||
bool _isLoading = true;
|
||||
final _emailController = TextEditingController();
|
||||
final _addressController = TextEditingController();
|
||||
final _nameController = TextEditingController();
|
||||
|
||||
Future<void> _addOrderToHistory() async {
|
||||
String name = _nameController.text;
|
||||
String email = _emailController.text;
|
||||
String address = _addressController.text;
|
||||
|
||||
Set<String> supplierIdsSet = {};
|
||||
for (final item in cartItems) {
|
||||
supplierIdsSet.add(item.supplierId);
|
||||
}
|
||||
List<GymHistoryItemDetailProvider> providers = [];
|
||||
for (final supplierId in supplierIdsSet) {
|
||||
List<GymItem> items =
|
||||
cartItems.where((e) => e.supplierId == supplierId).toList();
|
||||
List<GymHistoryItemDetailItem> detailItems = [];
|
||||
for (final item in items) {
|
||||
detailItems.add(GymHistoryItemDetailItem(
|
||||
id: item.id,
|
||||
photo: item.images[0].url,
|
||||
count: item.localCount,
|
||||
price: item.price.toString(),
|
||||
name: item.title,
|
||||
));
|
||||
}
|
||||
GymHistoryItemDetailProvider provider = GymHistoryItemDetailProvider(
|
||||
id: supplierId,
|
||||
name: items.first.supplierName,
|
||||
items: detailItems,
|
||||
// status: 'Не оплачен'
|
||||
status: Random().nextBool()
|
||||
? 'Не оплачен'
|
||||
: Random().nextBool()
|
||||
? 'Не оплачен'
|
||||
: Random().nextBool()
|
||||
? 'Сборка'
|
||||
: 'Доставляется',
|
||||
);
|
||||
providers.add(provider);
|
||||
}
|
||||
|
||||
final order = GymHistoryItemDetail(
|
||||
id: Random().nextInt(1000000).toString(),
|
||||
receiver: name,
|
||||
email: email,
|
||||
address: address,
|
||||
sum: totalPrice.toString(),
|
||||
date: '',
|
||||
providers: providers,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
);
|
||||
await addToHistory(order);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.microtask(() => getCart().then((value) async {
|
||||
final itemIds =
|
||||
value.map((element) => element['id'] as String).toList();
|
||||
final items = await getItemsByIds(context, itemIds);
|
||||
setState(() {
|
||||
gymCart = items;
|
||||
cartItems = value.map((element) {
|
||||
final item = gymCart.firstWhere((e) => e.id == element['id']);
|
||||
item.localCount = element['count'] as int;
|
||||
return item;
|
||||
}).toList();
|
||||
totalPrice = cartItems.fold(
|
||||
0, (sum, item) => sum + item.price * item.localCount);
|
||||
_isLoading = false;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Future<void> _goToPage() async {
|
||||
final Uri url = Uri.parse('https://example.org');
|
||||
if (!await launchUrl(url, webOnlyWindowName: '_blank')) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
appBar: const GymLinkAppBar(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const GymLinkHeader(title: 'Оформление заказа'),
|
||||
const MarkdownBody(data: '## Состав заказа:'),
|
||||
Expanded(
|
||||
child: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 350),
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: cartItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = cartItems[index];
|
||||
return OrderConfirmItemCard(
|
||||
name: shortString(item.title),
|
||||
image: FutureBuilder(
|
||||
future: precacheImage(
|
||||
NetworkImage(item.images[0].url), context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
return Image(
|
||||
image: NetworkImage(item.images[0].url),
|
||||
width: 50,
|
||||
height: 50,
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
price: item.price,
|
||||
count: item.localCount,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownBody(
|
||||
data: '## Итого: ${totalPrice.toStringAsFixed(2)} руб.'),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _addressController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Адрес доставки',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Электронная почта',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Получатель',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (!_checkInputs()) return;
|
||||
_goToPage();
|
||||
await clearCart();
|
||||
await _addOrderToHistory();
|
||||
Provider.of<CartProvider>(context, listen: false)
|
||||
.updateCartLength();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
CustomPageRoute(
|
||||
builder: (context) => const HistoryPage()),
|
||||
(route) => route.isFirst);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: const Text('Оформить заказ'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,191 @@
|
||||
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:lazy_load_scrollview/lazy_load_scrollview.dart';
|
||||
|
||||
class HistoryPage extends StatelessWidget {
|
||||
List<Map<String, String>> orders = [
|
||||
{"image": "product.png", "price": "120", "id": "66", "date": "11.09.2001"},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "150",
|
||||
"id": "56",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "250",
|
||||
"id": "98",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "300",
|
||||
"id": "50",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "100",
|
||||
"id": "30",
|
||||
"date": "11.09.2001",
|
||||
}
|
||||
];
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
const HistoryPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HistoryPage> createState() => _HistoryPageState();
|
||||
}
|
||||
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
List<GymHistoryItem> my_orders = [];
|
||||
late Timer _updateTimer;
|
||||
bool _isLoading = true;
|
||||
bool _isRefreshing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
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));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_updateTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onRefresh(Timer timer) async {
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
var orders = await getHistory();
|
||||
setState(() {
|
||||
my_orders = orders;
|
||||
_isLoading = false;
|
||||
_isRefreshing = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
appBar: GymLinkAppBar(),
|
||||
body: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||
GymLinkHeader(title: 'История заказов'),
|
||||
Center(
|
||||
child: Text('История заказов'),
|
||||
)
|
||||
]),
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const GymLinkHeader(
|
||||
title: 'История заказов',
|
||||
toMain: true,
|
||||
),
|
||||
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: double.parse(item.sum)
|
||||
.toStringAsFixed(2),
|
||||
date: item.date,
|
||||
image: FutureBuilder(
|
||||
future: precacheImage(
|
||||
NetworkImage(item.photo),
|
||||
context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
return Image(
|
||||
image: NetworkImage(
|
||||
item.photo),
|
||||
width: 50,
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// my_orders.isEmpty
|
||||
// ? const SizedBox.shrink()
|
||||
// : getSpacer(context: context)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
265
lib/pages/order_info.dart
Normal file
265
lib/pages/order_info.dart
Normal file
@@ -0,0 +1,265 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
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/components/order_detail_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/text.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final GymHistoryItemDetail item = GymHistoryItemDetail.fromJson({
|
||||
"id": "12345",
|
||||
"date": "01.01.1970",
|
||||
"sum": "45000",
|
||||
"pay_url": "https://example.org",
|
||||
"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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
class OrderInfoPage extends StatefulWidget {
|
||||
final String id;
|
||||
const OrderInfoPage({super.key, required this.id});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _OrderInfoPageState();
|
||||
}
|
||||
|
||||
class _OrderInfoPageState extends State<OrderInfoPage> {
|
||||
GymHistoryItemDetail? detail;
|
||||
final _scrollController = ScrollController();
|
||||
late Timer _updateTimer;
|
||||
bool _isRefreshing = false;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh);
|
||||
_onRefresh(_updateTimer);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_updateTimer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onRefresh(Timer timer) async {
|
||||
return Future.delayed(const Duration(milliseconds: 1000), () async {
|
||||
var orderInfo = await getHistoryDetail(widget.id);
|
||||
setState(() {
|
||||
detail = orderInfo;
|
||||
_isRefreshing = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _goToPage() async {
|
||||
if (detail?.payUrl != null) {
|
||||
final Uri url = Uri.parse(detail?.payUrl ?? 'https://example.org');
|
||||
if (!await launchUrl(url, webOnlyWindowName: '_blank')) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
_onRefresh(_updateTimer);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return Column(
|
||||
children: [
|
||||
GymLinkHeader(title: "Заказ #${detail?.id} от ${detail?.date}"),
|
||||
Expanded(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () => _onRefresh(_updateTimer),
|
||||
edgeOffset: 55,
|
||||
child: Scrollbar(
|
||||
controller: _scrollController,
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
kIsWeb
|
||||
? 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),
|
||||
ListView.builder(
|
||||
itemCount: detail!.providers.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final provider = detail!.providers[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 5,
|
||||
),
|
||||
child: 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(
|
||||
NetworkImage(item.photo), context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
return Image(
|
||||
image: NetworkImage(item.photo),
|
||||
width: 50,
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
),
|
||||
name: shortString(item.name),
|
||||
count: item.count,
|
||||
price: double.parse(item.price),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 5,
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 10, vertical: 20),
|
||||
child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MarkdownBody(
|
||||
data:
|
||||
'## Итого: ${double.parse(detail!.sum).toStringAsFixed(2)} руб.'),
|
||||
MarkdownBody(
|
||||
data:
|
||||
"### Адрес получателя: __${detail!.address}__"),
|
||||
MarkdownBody(
|
||||
data: '### Почта: __${detail!.email}__'),
|
||||
MarkdownBody(
|
||||
data: '### ФИО: __${detail!.receiver}__'),
|
||||
],
|
||||
),
|
||||
detail?.payUrl == null
|
||||
? const SizedBox.shrink()
|
||||
: Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _goToPage();
|
||||
await payOrder(detail!.id);
|
||||
},
|
||||
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 Text('Оплатить заказ'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
body: detail == null
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _buildContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/pages/order_pay.dart
Normal file
26
lib/pages/order_pay.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'dart:ui_web' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:universal_html/html.dart';
|
||||
|
||||
class OrderPayPage extends StatelessWidget {
|
||||
const OrderPayPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
registerHtmlElementView();
|
||||
return const HtmlElementView(
|
||||
viewType: 'payment-html',
|
||||
);
|
||||
}
|
||||
|
||||
void registerHtmlElementView() {
|
||||
ui.platformViewRegistry.registerViewFactory(
|
||||
'payment-html',
|
||||
(int viewId) => IFrameElement()
|
||||
..src = 'payment.html'
|
||||
..style.width = '100%'
|
||||
..style.height = '500px'
|
||||
..style.border = 'none');
|
||||
}
|
||||
}
|
||||
20
lib/providers/cart.dart
Normal file
20
lib/providers/cart.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/tools/prefs.dart';
|
||||
|
||||
//TODO: Возможно нужно дорабатывать
|
||||
class CartProvider extends ChangeNotifier {
|
||||
int _cartLength = 0;
|
||||
|
||||
int get cartLength => _cartLength;
|
||||
|
||||
CartProvider() {
|
||||
updateCartLength();
|
||||
}
|
||||
|
||||
void updateCartLength() {
|
||||
getCart().then((value) {
|
||||
_cartLength = value.length;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
42
lib/providers/main.dart
Normal file
42
lib/providers/main.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/theme.dart';
|
||||
|
||||
class GymLinkProvider with ChangeNotifier {
|
||||
bool _isLoading = true;
|
||||
bool get isLoading => _isLoading;
|
||||
bool _blackTheme = false;
|
||||
bool get blackTheme => _blackTheme;
|
||||
String _token = '';
|
||||
String get token => _token;
|
||||
ThemeData _theme = myTheme;
|
||||
ThemeData get theme => _theme;
|
||||
void Function() _onError = () => {};
|
||||
|
||||
void Function() get onError => _onError;
|
||||
|
||||
void checkToken(String token) {
|
||||
_token = token;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void changeTheme(int color, {bool blackTheme = false}) {
|
||||
_blackTheme = blackTheme;
|
||||
_theme = getThemeData(Color(color), _blackTheme);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTheme(ThemeData theme) {
|
||||
_theme = theme;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setOnError(void Function() onError) {
|
||||
_onError = () {
|
||||
_token = '';
|
||||
_isLoading = true;
|
||||
onError();
|
||||
notifyListeners();
|
||||
};
|
||||
}
|
||||
}
|
||||
32
lib/states/mobile.dart
Normal file
32
lib/states/mobile.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/main_mobile.dart';
|
||||
import 'package:gymlink_module_web/pages/main.dart';
|
||||
import 'package:gymlink_module_web/providers/cart.dart';
|
||||
import 'package:gymlink_module_web/providers/main.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MyAppStateMobile extends State<MyApp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<GymLinkProvider>(
|
||||
builder: (context, provider, __) {
|
||||
final theme = provider.theme;
|
||||
final isLoading = provider.isLoading;
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => CartProvider(),
|
||||
builder: (context, __) => isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: theme,
|
||||
themeMode: context.read<GymLinkProvider>().blackTheme
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const MainPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
64
lib/states/web.dart
Normal file
64
lib/states/web.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'dart:async';
|
||||
import 'dart:js_interop' as js;
|
||||
import 'dart:js_interop_unsafe' as js_util;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/main.dart';
|
||||
import 'package:gymlink_module_web/pages/main.dart';
|
||||
import 'package:gymlink_module_web/providers/cart.dart';
|
||||
import 'package:gymlink_module_web/providers/main.dart';
|
||||
import 'package:gymlink_module_web/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@js.JSExport()
|
||||
class MyAppStateWeb extends State<MyApp> {
|
||||
final _streamController = StreamController<void>.broadcast();
|
||||
ThemeData theme = myTheme;
|
||||
bool black_theme = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final export = js.createJSInteropWrapper(this);
|
||||
js.globalContext['_appState'] = export;
|
||||
js.globalContext.callMethod('_stateSet'.toJS);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamController.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<GymLinkProvider>().theme;
|
||||
final isLoading = context.watch<GymLinkProvider>().isLoading;
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => CartProvider(),
|
||||
child: isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const MainPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void checkToken(String token) {
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void changeColor(int color, bool blackTheme) {
|
||||
context.read<GymLinkProvider>().changeTheme(color, blackTheme: blackTheme);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void setOnError(void Function() onError) {
|
||||
context.read<GymLinkProvider>().setOnError(onError);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,19 @@ final ThemeData myTheme = ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: getMaterialColor(const Color(0x007d85ff))));
|
||||
|
||||
ThemeData getThemeData(Color color, bool dark) {
|
||||
final MaterialColor materialColor = getMaterialColor(color);
|
||||
return ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: materialColor,
|
||||
brightness: dark ? Brightness.dark : Brightness.light,
|
||||
).copyWith(
|
||||
onPrimary: dark ? materialColor[600] : Colors.white,
|
||||
),
|
||||
// useMaterial3: true,
|
||||
);
|
||||
}
|
||||
|
||||
MaterialColor getMaterialColor(Color color) {
|
||||
final int red = color.red;
|
||||
final int green = color.green;
|
||||
|
||||
99
lib/tools/history.dart
Normal file
99
lib/tools/history.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:gymlink_module_web/interfaces/items.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
Future<List<GymHistoryItem>> getHistory() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String historyString = prefs.getString('history') ?? "[]";
|
||||
List<GymHistoryItem> history = [];
|
||||
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
|
||||
history.add(GymHistoryItem.fromJson(historyItem));
|
||||
}
|
||||
history.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
||||
return history;
|
||||
}
|
||||
|
||||
Future<void> addToHistory(GymHistoryItemDetail item) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String historyString = prefs.getString('history') ?? "[]";
|
||||
List<GymHistoryItem> history = [];
|
||||
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
|
||||
history.add(GymHistoryItem.fromJson(historyItem));
|
||||
}
|
||||
item.id = Random().nextInt(100000).toString();
|
||||
String detailHistoryString = prefs.getString('detail_history') ?? "[]";
|
||||
List<GymHistoryItemDetail> detailHistory = [];
|
||||
for (var historyItem in jsonDecode(detailHistoryString) as List<dynamic>) {
|
||||
detailHistory.add(GymHistoryItemDetail.fromJson(historyItem));
|
||||
}
|
||||
List<Map<String, dynamic>> providers = [];
|
||||
for (final provider in item.providers) {
|
||||
providers.add(provider.toJson());
|
||||
}
|
||||
var json = {
|
||||
"id": item.id,
|
||||
"date": DateTime.now()
|
||||
.toLocal()
|
||||
.toString()
|
||||
.split(' ')[0]
|
||||
.replaceAll('-', '.')
|
||||
.split('.')
|
||||
.reversed
|
||||
.join('.'),
|
||||
"sum": item.sum,
|
||||
"pay_url": item.providers.where((e) => e.status == 'Не оплачен').isNotEmpty
|
||||
? 'https://example.org'
|
||||
: null,
|
||||
"receiver": item.receiver,
|
||||
"email": item.email,
|
||||
"address": item.address,
|
||||
"providers": providers,
|
||||
"timestamp": DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
};
|
||||
final detailHistoryItem = GymHistoryItemDetail.fromJson(json);
|
||||
detailHistory.add(detailHistoryItem);
|
||||
history.add(GymHistoryItem(
|
||||
date: detailHistoryItem.date,
|
||||
id: detailHistoryItem.id,
|
||||
photo: detailHistoryItem.providers[0].items[0].photo,
|
||||
sum: detailHistoryItem.sum,
|
||||
timestamp: detailHistoryItem.timestamp,
|
||||
));
|
||||
prefs.setString('history', jsonEncode(history));
|
||||
prefs.setString('detail_history', jsonEncode(detailHistory));
|
||||
}
|
||||
|
||||
Future<GymHistoryItemDetail?> getHistoryDetail(String id) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String historyString = prefs.getString('detail_history') ?? "[]";
|
||||
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
|
||||
if (GymHistoryItemDetail.fromJson(historyItem).id == id) {
|
||||
return GymHistoryItemDetail.fromJson(historyItem);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> payOrder(String id) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String historyString = prefs.getString('detail_history') ?? "[]";
|
||||
List<GymHistoryItemDetail> history = [];
|
||||
for (var historyItem in jsonDecode(historyString) as List<dynamic>) {
|
||||
history.add(GymHistoryItemDetail.fromJson(historyItem));
|
||||
}
|
||||
List<GymHistoryItemDetail> newHistory = [];
|
||||
for (final historyItem in history) {
|
||||
if (historyItem.id == id) {
|
||||
for (final provider in historyItem.providers) {
|
||||
if (provider.status == 'Не оплачен') {
|
||||
provider.status = 'Оплачен';
|
||||
}
|
||||
}
|
||||
historyItem.payUrl = null;
|
||||
}
|
||||
newHistory.add(historyItem);
|
||||
}
|
||||
prefs.setString('detail_history', jsonEncode(newHistory));
|
||||
}
|
||||
97
lib/tools/items.dart
Normal file
97
lib/tools/items.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/interfaces/items.dart';
|
||||
import 'package:gymlink_module_web/providers/main.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
Future<List<GymItem>> getItems(BuildContext context,
|
||||
{String searchText = '', String categoryId = ''}) async {
|
||||
final token = context.read<GymLinkProvider>().token;
|
||||
if (token != '') {
|
||||
final Uri url = Uri.https('gymlink.freemyip.com', 'api/product/get-list');
|
||||
try {
|
||||
final response = await http.post(url,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: jsonEncode({
|
||||
"search": searchText,
|
||||
"page": 0,
|
||||
"pageSize": 0,
|
||||
"filter": categoryId,
|
||||
"direction": 1
|
||||
}));
|
||||
if (response.statusCode == 201) {
|
||||
final items = ItemsDataResponse.fromRawJson(response.body).rows;
|
||||
return items;
|
||||
}
|
||||
throw response.body;
|
||||
} catch (e) {
|
||||
debugPrint('error: $e');
|
||||
return await Future.delayed(
|
||||
const Duration(seconds: 5), () => getItems(context));
|
||||
}
|
||||
}
|
||||
context.read<GymLinkProvider>().onError();
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<List<GymItem>> getItemsByIds(
|
||||
BuildContext context, List<String> ids) async {
|
||||
final token = context.read<GymLinkProvider>().token;
|
||||
if (token != '') {
|
||||
if (ids.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
final Uri url =
|
||||
Uri.https('gymlink.freemyip.com', 'api/product/get-products');
|
||||
try {
|
||||
final response = await http.post(url,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: jsonEncode({"ids": ids}));
|
||||
if (response.statusCode == 201) {
|
||||
final data =
|
||||
jsonDecode(utf8.decode(response.bodyBytes)) as List<dynamic>;
|
||||
final items = data.map((e) => GymItem.fromJson(e)).toList();
|
||||
return items;
|
||||
}
|
||||
throw response.body;
|
||||
} catch (e) {
|
||||
debugPrint('error: $e');
|
||||
return await Future.delayed(
|
||||
const Duration(seconds: 5), () => getItemsByIds(context, ids));
|
||||
}
|
||||
}
|
||||
context.read<GymLinkProvider>().onError();
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<List<GymCategory>> getCategories(BuildContext context) async {
|
||||
final token = context.read<GymLinkProvider>().token;
|
||||
if (token != '') {
|
||||
final Uri url = Uri.https(
|
||||
'gymlink.freemyip.com', 'api/category/get-internal-categories');
|
||||
try {
|
||||
final response = await http.get(url, headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
});
|
||||
if (response.statusCode == 200) {
|
||||
final categories = CategoryDataResponse.fromRawJson(response.body).rows;
|
||||
return categories;
|
||||
}
|
||||
throw response.body;
|
||||
} catch (e) {
|
||||
debugPrint('error: $e');
|
||||
return await Future.delayed(
|
||||
const Duration(seconds: 5), () => getCategories(context));
|
||||
}
|
||||
}
|
||||
context.read<GymLinkProvider>().onError();
|
||||
return [];
|
||||
}
|
||||
@@ -40,3 +40,8 @@ Future<void> removeItemFromCart(String id) async {
|
||||
}
|
||||
prefs.setString('cart', jsonEncode(cart));
|
||||
}
|
||||
|
||||
Future<void> clearCart() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('cart', "[]");
|
||||
}
|
||||
|
||||
11
lib/tools/relative.dart
Normal file
11
lib/tools/relative.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget getSpacer(
|
||||
{required BuildContext context, int flex = 1, double width = 10}) {
|
||||
if (MediaQuery.of(context).size.width > 600) {
|
||||
return Spacer(
|
||||
flex: flex,
|
||||
);
|
||||
}
|
||||
return SizedBox(width: width);
|
||||
}
|
||||
8
lib/tools/routes.dart
Normal file
8
lib/tools/routes.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomPageRoute extends MaterialPageRoute {
|
||||
CustomPageRoute({builder}) : super(builder: builder);
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => const Duration(milliseconds: 0);
|
||||
}
|
||||
9
lib/tools/text.dart
Normal file
9
lib/tools/text.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
String shortString(String text, {int length = 10}) {
|
||||
if (text.length > length) {
|
||||
String shortText = text.substring(0, length);
|
||||
return shortText +
|
||||
(text.substring(0, length + 1).endsWith(' ') ? '' : '...');
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
180
pubspec.lock
180
pubspec.lock
@@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -17,6 +25,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
carousel_slider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: carousel_slider
|
||||
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -25,6 +41,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -41,14 +65,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -86,6 +118,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -96,6 +144,38 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
lazy_load_scrollview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lazy_load_scrollview
|
||||
sha256: "230c827d6f7ec5e461f0674ef332daae2f78190bf1e4cd84977e51de04b231e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -128,6 +208,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: markdown
|
||||
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -152,6 +240,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -160,6 +256,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -184,6 +288,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -200,6 +312,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -309,6 +429,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
universal_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: universal_html
|
||||
sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -373,6 +517,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.11+1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -413,6 +581,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
sdks:
|
||||
dart: ">=3.3.3 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
13
pubspec.yaml
13
pubspec.yaml
@@ -31,12 +31,16 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
url_launcher: ^6.2.6
|
||||
shared_preferences: ^2.2.3
|
||||
flutter_markdown: ^0.7.1
|
||||
http: ^1.2.1
|
||||
universal_html: ^2.2.4
|
||||
provider: ^6.1.2
|
||||
lazy_load_scrollview: ^1.3.0
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_svg: ^2.0.10+1
|
||||
carousel_slider: ^4.2.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -62,6 +66,7 @@ flutter:
|
||||
assets:
|
||||
- assets/logo.png
|
||||
- assets/product.png
|
||||
- assets/icon.svg
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
@@ -14,52 +14,56 @@
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
<base href="$FLUTTER_BASE_HREF" />
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="A new Flutter project.">
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
|
||||
<meta name="description" content="A new Flutter project." />
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="flutter_application_1">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<meta name="apple-mobile-web-app-title" content="flutter_application_1" />
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<title>flutter_application_1</title>
|
||||
<link rel='stylesheet' href='css/styles.css'>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>flutter_application_1</title>
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
|
||||
<!-- <script>
|
||||
<!-- <script>
|
||||
// The value below is injected by flutter build, do not touch.
|
||||
const serviceWorkerVersion = null;
|
||||
</script> -->
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<button id='token'>Token btn</button>
|
||||
<section class='contents'>
|
||||
<article>
|
||||
<div id="flutter_target"></div>
|
||||
</article>
|
||||
</section>
|
||||
<script>
|
||||
window.addEventListener('load', function(ev) {
|
||||
let target = document.querySelector('#flutter_target');
|
||||
_flutter.loader.loadEntrypoint({
|
||||
onEntrypointLoaded: async function (engineInitializer) {
|
||||
let appRunner = await engineInitializer.initializeEngine({
|
||||
hostElement: target,
|
||||
});
|
||||
await appRunner.runApp();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
<script src='js/demo-js-interop.js' defer></script>
|
||||
</body>
|
||||
<!-- This script adds the flutter initialization JS code -->
|
||||
<script src="flutter.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<button id="token">Token btn</button>
|
||||
<button id="token2">Token btn 2</button>
|
||||
<button id="colorChangeBtnRed">Color btn Red</button>
|
||||
<button id="colorChangeBtnBlue">Color btn Blue</button>
|
||||
<button id="clearBtn">Clear</button>
|
||||
<section class="contents">
|
||||
<article>
|
||||
<div id="flutter_target"></div>
|
||||
</article>
|
||||
</section>
|
||||
<script>
|
||||
window.addEventListener('load', function (ev) {
|
||||
let target = document.querySelector('#flutter_target');
|
||||
_flutter.loader.loadEntrypoint({
|
||||
onEntrypointLoaded: async function (engineInitializer) {
|
||||
let appRunner = await engineInitializer.initializeEngine({
|
||||
hostElement: target,
|
||||
});
|
||||
await appRunner.runApp();
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="js/demo-js-interop.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,16 +1,70 @@
|
||||
(function(){
|
||||
"use strict";
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window._stateSet = function () {
|
||||
window._stateSet = function() {
|
||||
console.log("Call _stateSet only once");
|
||||
window._stateSet = function () {
|
||||
console.log('Call _stateSet only once');
|
||||
};
|
||||
let appState = window._appState;
|
||||
|
||||
let btn = document.getElementById('token');
|
||||
btn.addEventListener('click', function() {
|
||||
appState.onTokenReceived('token123');
|
||||
})
|
||||
|
||||
}
|
||||
}());
|
||||
function getToken(token) {
|
||||
fetch('https://gymlink.freemyip.com/api/auth/authorize_client', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
GymKey: token, // Just testing token
|
||||
id: '123',
|
||||
}),
|
||||
})
|
||||
.then(res => res.json())
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
setTimeout(getToken, 1000);
|
||||
})
|
||||
.then(data => {
|
||||
if (data.payload) appState.checkToken(data.payload.token);
|
||||
else {
|
||||
console.log(data);
|
||||
setTimeout(getToken, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const btn = document.getElementById('token');
|
||||
btn.addEventListener('click', () => {
|
||||
localStorage.clear();
|
||||
getToken('eeb42dcb-8e5b-4f21-825a-3fc7ada43445');
|
||||
});
|
||||
|
||||
const btn2 = document.getElementById('token2');
|
||||
btn2.addEventListener('click', () => {
|
||||
localStorage.clear();
|
||||
getToken('a8622a61-3142-487e-8db8-b6aebd4f04aa');
|
||||
});
|
||||
|
||||
let colorChangeBtnRed = document.getElementById('colorChangeBtnRed');
|
||||
colorChangeBtnRed.addEventListener('click', function () {
|
||||
var hexColor = '#FF0000'.substring(1);
|
||||
var numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor, true);
|
||||
});
|
||||
|
||||
let colorChangeBtnBlue = document.getElementById('colorChangeBtnBlue');
|
||||
colorChangeBtnBlue.addEventListener('click', function () {
|
||||
var hexColor = '#0000FF'.substring(1);
|
||||
var numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor, false);
|
||||
});
|
||||
let clearBtn = document.getElementById('clearBtn');
|
||||
clearBtn.addEventListener('click', function () {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
function onError() {
|
||||
console.error('Error');
|
||||
}
|
||||
|
||||
appState.setOnError(onError);
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user