Compare commits
18 Commits
04ee6d1699
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f182c80210 | |||
| cefabd1c70 | |||
| f1055d40a4 | |||
| cfe0184e3a | |||
| faa52dcaa2 | |||
| 946d2ada41 | |||
| 7cb92a7b83 | |||
| dd59a605ad | |||
| 34c0ea5fa1 | |||
| 893b925a04 | |||
| bdcd4507c2 | |||
| fb5538ab79 | |||
| 0a22b5c051 | |||
| 73fe273c75 | |||
| f5e1407281 | |||
| 27da063c34 | |||
| c0c3ef2ca0 | |||
| 9335e8e694 |
@@ -1,4 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:label="Example Gym App"
|
||||
android:name="${applicationName}"
|
||||
@@ -31,7 +32,6 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility?hl=en and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
@@ -22,6 +22,11 @@ class GymLinkAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
width: 24,
|
||||
height: 24,
|
||||
semanticsLabel: 'GymLink Logo',
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
|
||||
@@ -6,7 +6,7 @@ class BasketItemCard extends StatelessWidget {
|
||||
final String name;
|
||||
final String price;
|
||||
final String id;
|
||||
final Image image;
|
||||
final Widget image;
|
||||
final String quantity;
|
||||
final VoidCallback onTapPlus;
|
||||
final VoidCallback onTapMinus;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:gymlink_module_web/pages/order_info.dart';
|
||||
import 'package:gymlink_module_web/tools/routes.dart';
|
||||
|
||||
enum OrderStatus { created, inProgress, completed, canceled }
|
||||
|
||||
@@ -14,15 +16,13 @@ class HistoryItemCard extends StatelessWidget {
|
||||
final String id;
|
||||
final String cost;
|
||||
final String date;
|
||||
final Image image;
|
||||
final OrderStatus status;
|
||||
final Widget image;
|
||||
|
||||
const HistoryItemCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.cost,
|
||||
required this.date,
|
||||
required this.status,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
@@ -38,38 +38,42 @@ class HistoryItemCard extends StatelessWidget {
|
||||
minWidth: 600,
|
||||
maxWidth: 800,
|
||||
),
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
image,
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MarkdownBody(
|
||||
data: '### Заказ **№$id** от $date',
|
||||
),
|
||||
MarkdownBody(
|
||||
data: 'Статус: **${orderStatusMap[status]}**'),
|
||||
MarkdownBody(data: 'Сумма: **$cost руб.**'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
CustomPageRoute(builder: (context) => OrderInfoPage(id: id)));
|
||||
},
|
||||
child: Card(
|
||||
elevation: 4,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
image,
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MarkdownBody(
|
||||
data: '### Заказ **№$id** от $date',
|
||||
),
|
||||
MarkdownBody(data: 'Сумма: **$cost руб.**'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
@@ -15,10 +15,10 @@ class ProductCard extends StatelessWidget {
|
||||
});
|
||||
|
||||
double getCardHeight({required BuildContext context}) {
|
||||
if (MediaQuery.of(context).size.width > 600) {
|
||||
return 200;
|
||||
if (MediaQuery.of(context).size.width > 400) {
|
||||
return 300;
|
||||
}
|
||||
return 100;
|
||||
return 160;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -27,7 +27,10 @@ class ProductCard extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 80, maxHeight: getCardHeight(context: context)),
|
||||
minHeight: 160,
|
||||
maxHeight: getCardHeight(context: context),
|
||||
minWidth: 180,
|
||||
maxWidth: 250),
|
||||
child: Card(
|
||||
elevation: 3,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
@@ -42,7 +45,8 @@ class ProductCard extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
|
||||
@@ -5,7 +5,7 @@ class OrderConfirmItemCard extends StatelessWidget {
|
||||
final String name;
|
||||
final int count;
|
||||
final double price;
|
||||
final Image image;
|
||||
final Widget image;
|
||||
|
||||
const OrderConfirmItemCard({
|
||||
super.key,
|
||||
|
||||
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')
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ class GymItem {
|
||||
final double price;
|
||||
final String categoryId;
|
||||
final List<GymImage> images;
|
||||
final String supplierId;
|
||||
final String supplierName;
|
||||
int localCount = 0;
|
||||
|
||||
GymItem({
|
||||
@@ -42,23 +44,31 @@ class GymItem {
|
||||
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) => 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))),
|
||||
);
|
||||
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,
|
||||
@@ -69,6 +79,8 @@ class GymItem {
|
||||
"price": price,
|
||||
"categoryId": categoryId,
|
||||
"images": List<dynamic>.from(images.map((x) => x.toJson())),
|
||||
"supplier":
|
||||
supplierId == '' ? null : {"id": supplierId, "title": supplierName},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -148,3 +160,166 @@ class GymCategory {
|
||||
"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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@ 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 {
|
||||
debugPrint(token);
|
||||
var url = Uri.http('gymlink.freemyip.com:8080', 'api/auth/authorize_client');
|
||||
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
|
||||
@@ -32,6 +33,8 @@ class MyExampleApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
return MaterialApp(
|
||||
title: 'GymLink Example App',
|
||||
debugShowCheckedModeBanner: false,
|
||||
@@ -53,32 +56,49 @@ Widget getDrawer(BuildContext context) => Drawer(
|
||||
children: [
|
||||
const DrawerHeader(child: Text('Drawer Header')),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.home),
|
||||
title: const Text('Home'),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ExampleMainPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.sell),
|
||||
title: const Text('Club 2'),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChangeNotifierProvider(
|
||||
create: (_) => GymLinkProvider(),
|
||||
child: Consumer<GymLinkProvider>(
|
||||
builder: (_, value, __) => const ExampleClub2Page(),
|
||||
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).push(MaterialPageRoute(
|
||||
onTap: () =>
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) => const ExampleSecondPage(),
|
||||
)),
|
||||
),
|
||||
@@ -129,7 +149,7 @@ class _ExamplePageState extends State<ExamplePage> {
|
||||
Future<void> _setToken() async {
|
||||
final token = await getToken('eeb42dcb-8e5b-4f21-825a-3fc7ada43445', '123');
|
||||
if (token != '') {
|
||||
context.read<GymLinkProvider>().onTokenReceived(token);
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
} else {
|
||||
context.read<GymLinkProvider>().onError();
|
||||
}
|
||||
@@ -143,26 +163,25 @@ class _ExamplePageState extends State<ExamplePage> {
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
drawer: getDrawer(context),
|
||||
body: const Column(
|
||||
body: Column(
|
||||
children: [
|
||||
Text('test'),
|
||||
Expanded(
|
||||
IconButton(
|
||||
icon: const Icon(Icons.colorize),
|
||||
onPressed: () {
|
||||
context.read<GymLinkProvider>().changeTheme(
|
||||
Random().nextInt(0xffffff + 1),
|
||||
blackTheme: Random().nextBool());
|
||||
},
|
||||
),
|
||||
const Expanded(
|
||||
child: MyApp(),
|
||||
),
|
||||
SizedBox(
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text('Bottom text')
|
||||
const Text('Bottom text')
|
||||
],
|
||||
),
|
||||
floatingActionButton: IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
context.read<GymLinkProvider>().changeTheme(
|
||||
Random().nextInt(0xffffff + 1),
|
||||
blackTheme: Random().nextBool());
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +224,7 @@ class _ExampleClub2PageState extends State<ExampleClub2Page> {
|
||||
final token = await getToken('a8622a61-3142-487e-8db8-b6aebd4f04aa', '123');
|
||||
context.read<GymLinkProvider>().changeTheme(0xFFAABCAB);
|
||||
if (token != '') {
|
||||
context.read<GymLinkProvider>().onTokenReceived(token);
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
} else {
|
||||
context.read<GymLinkProvider>().onError();
|
||||
}
|
||||
@@ -231,12 +250,6 @@ class _ExampleClub2PageState extends State<ExampleClub2Page> {
|
||||
Text('Bottom text')
|
||||
],
|
||||
),
|
||||
floatingActionButton: IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: () {
|
||||
// context.read<GymLinkProvider>().changeTheme(0xFFAABCAB);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -251,8 +264,16 @@ class ExampleSecondPage extends StatelessWidget {
|
||||
title: const Text('GymLink Example App'),
|
||||
),
|
||||
drawer: getDrawer(context),
|
||||
body: const Center(
|
||||
child: Text('Example page'),
|
||||
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')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
|
||||
@@ -154,6 +155,8 @@ class _BasketPageState extends State<BasketPage> {
|
||||
removeItemFromCart(id);
|
||||
setState(() {
|
||||
cartItems.removeWhere((element) => element.id == id);
|
||||
totalPrice = cartItems.fold(
|
||||
0, (sum, item) => sum + item.price * item.localCount);
|
||||
});
|
||||
if (mounted) {
|
||||
_updateCart();
|
||||
@@ -279,12 +282,25 @@ class _BasketPageState extends State<BasketPage> {
|
||||
itemBuilder: (context, index) {
|
||||
final item = cartItems[index];
|
||||
return BasketItemCard(
|
||||
name: item.title,
|
||||
name: shortString(item.title),
|
||||
price: item.price.toStringAsFixed(2),
|
||||
id: item.id,
|
||||
image: Image(
|
||||
image: NetworkImage(item.images[0].url),
|
||||
width: 50,
|
||||
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()),
|
||||
@@ -297,7 +313,7 @@ class _BasketPageState extends State<BasketPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildSpacer(),
|
||||
// _buildSpacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 10, vertical: 10),
|
||||
@@ -340,7 +356,7 @@ class _BasketPageState extends State<BasketPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 50),
|
||||
// const SizedBox(width: 50),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -53,7 +53,7 @@ class _DetailPageState extends State<DetailPage> {
|
||||
|
||||
Future<void> _getItem() async {
|
||||
final Uri url =
|
||||
Uri.http('gymlink.freemyip.com:8080', 'api/product/get/${widget.id}');
|
||||
Uri.https('gymlink.freemyip.com', 'api/product/get/${widget.id}');
|
||||
final response = await http.get(url, headers: {
|
||||
'Authorization': 'Bearer ${context.read<GymLinkProvider>().token}',
|
||||
});
|
||||
@@ -88,12 +88,6 @@ class _DetailPageState extends State<DetailPage> {
|
||||
required BuildContext context,
|
||||
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceAround,
|
||||
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center}) {
|
||||
// if (false && MediaQuery.of(context).size.width > 600) {
|
||||
// return Row(
|
||||
// mainAxisAlignment: mainAxisAlignment,
|
||||
// crossAxisAlignment: crossAxisAlignment,
|
||||
// children: children);
|
||||
// }
|
||||
return Column(
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
@@ -202,7 +196,6 @@ class _DetailPageState extends State<DetailPage> {
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
// height: MediaQuery.sizeOf(context).height,
|
||||
child: _buildRowOrCol(
|
||||
context: context,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
@@ -288,12 +281,14 @@ class _DetailPageState extends State<DetailPage> {
|
||||
: categoryName!)
|
||||
: ''),
|
||||
backgroundColor: Colors.white,
|
||||
labelStyle:
|
||||
const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: MarkdownBody(
|
||||
data: '### Отстаток: _${item!.count}_',
|
||||
data: '### Остаток: _${item!.count}_',
|
||||
)),
|
||||
item!.description != ''
|
||||
? Padding(
|
||||
|
||||
@@ -10,7 +10,6 @@ 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/relative.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';
|
||||
@@ -73,6 +72,8 @@ class _MainPageState extends State<MainPage> {
|
||||
bool isSearching = false;
|
||||
List<GymCategory> categories = [];
|
||||
GymCategory? selectedCategory;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final TextEditingController _searchField = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -110,7 +111,6 @@ class _MainPageState extends State<MainPage> {
|
||||
setState(() {
|
||||
filteredData = data;
|
||||
itemViewCount = min(filteredData.length, 5);
|
||||
debugPrint(itemViewCount.toString());
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
for (var element in filteredData.sublist(0, itemViewCount)) {
|
||||
@@ -122,8 +122,11 @@ class _MainPageState extends State<MainPage> {
|
||||
});
|
||||
}
|
||||
|
||||
void _onSearch() async {
|
||||
void _onSearch() {
|
||||
final categoryId = selectedCategory == null ? '' : selectedCategory!.id;
|
||||
setState(() {
|
||||
searchText = _searchField.text.trim().toLowerCase();
|
||||
});
|
||||
_searchItems(searchText: searchText, categoryId: categoryId);
|
||||
}
|
||||
|
||||
@@ -142,21 +145,22 @@ class _MainPageState extends State<MainPage> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
onChanged: (value) => setState(() {
|
||||
onChanged: (value) {
|
||||
searchText = value.trim().toLowerCase();
|
||||
if (searchText == '') {
|
||||
_onSearch();
|
||||
}
|
||||
}),
|
||||
},
|
||||
controller: _searchField,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (_) => _onSearch(),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Поиск',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: ElevatedButton(
|
||||
onPressed: _onSearch,
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -179,7 +183,7 @@ class _MainPageState extends State<MainPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
getSpacer(context: context, flex: 2),
|
||||
// getSpacer(context: context, flex: 2),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
@@ -212,45 +216,50 @@ class _MainPageState extends State<MainPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
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),
|
||||
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) &&
|
||||
@@ -265,19 +274,32 @@ class _MainPageState extends State<MainPage> {
|
||||
SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: min(
|
||||
(MediaQuery.sizeOf(context).width ~/
|
||||
200)
|
||||
220)
|
||||
.toInt(),
|
||||
8),
|
||||
childAspectRatio: 1.0),
|
||||
childAspectRatio: 0.8,
|
||||
mainAxisSpacing: 10.0,
|
||||
crossAxisSpacing: 20.0),
|
||||
itemCount: itemViewCount,
|
||||
itemBuilder: (context, index) {
|
||||
final product = filteredData[index];
|
||||
return ProductCard(
|
||||
imagePath: Image(
|
||||
image:
|
||||
Image.network(product.images[0].url)
|
||||
.image,
|
||||
width: 50,
|
||||
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),
|
||||
@@ -307,9 +329,17 @@ class _MainPageState extends State<MainPage> {
|
||||
Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
fixedSize: const Size(180, 40),
|
||||
),
|
||||
child: const Text('Загрузить ещё'),
|
||||
)
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Загрузить ещё'),
|
||||
Spacer(),
|
||||
Icon(Icons.arrow_downward),
|
||||
Spacer()
|
||||
],
|
||||
))
|
||||
: const CircularProgressIndicator()
|
||||
: const Text(
|
||||
'Конец списка',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:gymlink_module_web/components/app_bar.dart';
|
||||
@@ -6,9 +8,11 @@ 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';
|
||||
|
||||
@@ -63,6 +67,61 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
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() {
|
||||
@@ -86,12 +145,71 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
}
|
||||
|
||||
Future<void> _goToPage() async {
|
||||
final Uri url = Uri.parse('https://google.com');
|
||||
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(
|
||||
@@ -113,11 +231,23 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
itemBuilder: (context, index) {
|
||||
final item = cartItems[index];
|
||||
return OrderConfirmItemCard(
|
||||
name: item.title,
|
||||
image: Image(
|
||||
image: NetworkImage(item.images[0].url),
|
||||
width: 50,
|
||||
height: 50),
|
||||
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,
|
||||
);
|
||||
@@ -135,6 +265,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
data: '## Итого: ${totalPrice.toStringAsFixed(2)} руб.'),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _addressController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Адрес доставки',
|
||||
border: OutlineInputBorder(
|
||||
@@ -145,6 +276,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Электронная почта',
|
||||
border: OutlineInputBorder(
|
||||
@@ -156,6 +288,7 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Получатель',
|
||||
border: OutlineInputBorder(
|
||||
@@ -164,19 +297,12 @@ class _OrderConfirmationPageState extends State<OrderConfirmationPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Согласен с обаботкой персональных данных'),
|
||||
value: isAgree,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
isAgree = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (!_checkInputs()) return;
|
||||
_goToPage();
|
||||
await clearCart();
|
||||
await _addOrderToHistory();
|
||||
Provider.of<CartProvider>(context, listen: false)
|
||||
.updateCartLength();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gymlink_module_web/components/app_bar.dart';
|
||||
import 'package:gymlink_module_web/components/heading.dart';
|
||||
import 'package:gymlink_module_web/components/history_item_card.dart';
|
||||
import 'package:gymlink_module_web/tools/relative.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';
|
||||
|
||||
List<Map<String, String>> orders = [
|
||||
@@ -45,32 +47,24 @@ class HistoryPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
List<Map<String, String>> my_orders = [];
|
||||
List<GymHistoryItem> my_orders = [];
|
||||
late Timer _updateTimer;
|
||||
bool _isLoading = true;
|
||||
bool _isRefreshing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
my_orders = orders;
|
||||
ordersRefresh();
|
||||
}
|
||||
|
||||
void ordersRefresh() {
|
||||
_updateTimer = Timer.periodic(const Duration(minutes: 1), _onRefresh);
|
||||
Future.microtask(() => _onRefresh(_updateTimer));
|
||||
}
|
||||
|
||||
void _onLoad() async {
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
setState(() {
|
||||
my_orders.add(
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "120",
|
||||
"id": "666666",
|
||||
"date": "11.09.2001"
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -81,7 +75,12 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
|
||||
Future<void> _onRefresh(Timer timer) async {
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
debugPrint('refreshed');
|
||||
var orders = await getHistory();
|
||||
setState(() {
|
||||
my_orders = orders;
|
||||
_isLoading = false;
|
||||
_isRefreshing = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -95,43 +94,96 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
title: 'История заказов',
|
||||
toMain: true,
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: _onLoad,
|
||||
scrollOffset: 200,
|
||||
child: RefreshIndicator(
|
||||
edgeOffset: 55,
|
||||
onRefresh: () => _onRefresh(_updateTimer),
|
||||
child: Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
itemCount: my_orders.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = my_orders[index];
|
||||
return HistoryItemCard(
|
||||
id: item['id']!,
|
||||
cost: item['price']!,
|
||||
date: item['date']!,
|
||||
image: Image(
|
||||
image: AssetImage('assets/${item['image']!}'),
|
||||
width: 50,
|
||||
),
|
||||
status: OrderStatus.completed,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
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)
|
||||
],
|
||||
),
|
||||
),
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,10 @@ class GymLinkProvider with ChangeNotifier {
|
||||
|
||||
void Function() get onError => _onError;
|
||||
|
||||
void onTokenReceived(String token) {
|
||||
void checkToken(String token) {
|
||||
_token = token;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
// if (token == 'token123') {
|
||||
// _isLoading = false;
|
||||
// notifyListeners();
|
||||
// } else {
|
||||
// _isLoading = true;
|
||||
// notifyListeners();
|
||||
// }
|
||||
}
|
||||
|
||||
void changeTheme(int color, {bool blackTheme = false}) {
|
||||
|
||||
@@ -19,6 +19,9 @@ class MyAppStateMobile extends State<MyApp> {
|
||||
: MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: theme,
|
||||
themeMode: context.read<GymLinkProvider>().blackTheme
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const MainPage(),
|
||||
),
|
||||
|
||||
@@ -48,13 +48,13 @@ class MyAppStateWeb extends State<MyApp> {
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void onTokenReceived(String token) {
|
||||
context.read<GymLinkProvider>().onTokenReceived(token);
|
||||
void checkToken(String token) {
|
||||
context.read<GymLinkProvider>().checkToken(token);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void changeColor(int color) {
|
||||
context.read<GymLinkProvider>().changeTheme(color);
|
||||
void changeColor(int color, bool blackTheme) {
|
||||
context.read<GymLinkProvider>().changeTheme(color, blackTheme: blackTheme);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
|
||||
@@ -13,7 +13,7 @@ ThemeData getThemeData(Color color, bool dark) {
|
||||
).copyWith(
|
||||
onPrimary: dark ? materialColor[600] : Colors.white,
|
||||
),
|
||||
useMaterial3: true,
|
||||
// useMaterial3: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -10,8 +10,7 @@ Future<List<GymItem>> getItems(BuildContext context,
|
||||
{String searchText = '', String categoryId = ''}) async {
|
||||
final token = context.read<GymLinkProvider>().token;
|
||||
if (token != '') {
|
||||
final Uri url =
|
||||
Uri.http('gymlink.freemyip.com:8080', 'api/product/get-list');
|
||||
final Uri url = Uri.https('gymlink.freemyip.com', 'api/product/get-list');
|
||||
try {
|
||||
final response = await http.post(url,
|
||||
headers: {
|
||||
@@ -48,7 +47,7 @@ Future<List<GymItem>> getItemsByIds(
|
||||
return [];
|
||||
}
|
||||
final Uri url =
|
||||
Uri.http('gymlink.freemyip.com:8080', 'api/product/get-products');
|
||||
Uri.https('gymlink.freemyip.com', 'api/product/get-products');
|
||||
try {
|
||||
final response = await http.post(url,
|
||||
headers: {
|
||||
@@ -76,8 +75,8 @@ Future<List<GymItem>> getItemsByIds(
|
||||
Future<List<GymCategory>> getCategories(BuildContext context) async {
|
||||
final token = context.read<GymLinkProvider>().token;
|
||||
if (token != '') {
|
||||
final Uri url = Uri.http(
|
||||
'gymlink.freemyip.com:8080', 'api/category/get-internal-categories');
|
||||
final Uri url = Uri.https(
|
||||
'gymlink.freemyip.com', 'api/category/get-internal-categories');
|
||||
try {
|
||||
final response = await http.get(url, headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
|
||||
@@ -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,54 +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>
|
||||
<button id='colorChangeBtnRed'>Color btn Red</button>
|
||||
<button id='colorChangeBtnBlue'>Color btn Blue</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>
|
||||
|
||||
@@ -7,26 +7,22 @@
|
||||
};
|
||||
let appState = window._appState;
|
||||
|
||||
function getToken() {
|
||||
fetch(
|
||||
'http://gymlink.freemyip.com:8080/api/auth/authorize_client',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
GymKey: 'eeb42dcb-8e5b-4f21-825a-3fc7ada43445', // Just testing token
|
||||
id: '123',
|
||||
}),
|
||||
}
|
||||
)
|
||||
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.onTokenReceived(data.payload.token);
|
||||
if (data.payload) appState.checkToken(data.payload.token);
|
||||
else {
|
||||
console.log(data);
|
||||
setTimeout(getToken, 1000);
|
||||
@@ -34,35 +30,39 @@
|
||||
});
|
||||
}
|
||||
|
||||
let btn = document.getElementById('token');
|
||||
btn.addEventListener('click', getToken);
|
||||
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'
|
||||
.replace(
|
||||
/^#?([a-f\d])([a-f\d])([a-f\d])$/i,
|
||||
(m, r, g, b) => '#ff' + r + r + g + g + b + b
|
||||
)
|
||||
.substring(1);
|
||||
var hexColor = '#FF0000'.substring(1);
|
||||
var numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor);
|
||||
appState.changeColor(numColor, true);
|
||||
});
|
||||
|
||||
let colorChangeBtnBlue = document.getElementById('colorChangeBtnBlue');
|
||||
colorChangeBtnBlue.addEventListener('click', function () {
|
||||
var hexColor = '#0000FF'
|
||||
.replace(
|
||||
/^#?([a-f\d])([a-f\d])([a-f\d])$/i,
|
||||
(m, r, g, b) => '#ff' + r + r + g + g + b + b
|
||||
)
|
||||
.substring(1);
|
||||
var hexColor = '#0000FF'.substring(1);
|
||||
var numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor);
|
||||
appState.changeColor(numColor, false);
|
||||
});
|
||||
let clearBtn = document.getElementById('clearBtn');
|
||||
clearBtn.addEventListener('click', function () {
|
||||
localStorage.clear();
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
function onError() {
|
||||
console.error('aboba');
|
||||
console.error('Error');
|
||||
}
|
||||
|
||||
appState.setOnError(onError);
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Прием платежа с помощью виджета ЮKassa</title>
|
||||
|
||||
<!--Подключение библиотеки для инициализации виджета ЮKassa-->
|
||||
<script src="https://yookassa.ru/checkout-widget/v1/checkout-widget.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
Ниже отобразится платежная форма. Если вы еще не создавали платеж и не передавали токен для инициализации виджета, появится сообщение об ошибке.
|
||||
|
||||
<!--Контейнер, в котором будет отображаться платежная форма-->
|
||||
<div id="payment-form"></div>
|
||||
|
||||
Данные банковской карты для оплаты в <b>тестовом магазине</b>:
|
||||
|
||||
- номер — <b>5555 5555 5555 4477</b>
|
||||
- срок действия — <b>01/30</b> (или другая дата, больше текущей)
|
||||
- CVC — <b>123</b> (или три любые цифры)
|
||||
- код для прохождения 3-D Secure — <b>123</b> (или три любые цифры)
|
||||
|
||||
<a href=https://yookassa.ru/developers/payment-acceptance/testing-and-going-live/testing#test-bank-card>Другие тестовые банковские карты</a>
|
||||
|
||||
<script>
|
||||
//Инициализация виджета. Все параметры обязательные.
|
||||
const checkout = new window.YooMoneyCheckoutWidget({
|
||||
confirmation_token: 'ct-287e0c37-000f-5000-8000-16961d35b0fd', //Токен, который перед проведением оплаты нужно получить от ЮKassa
|
||||
return_url: 'https://example.com/', //Ссылка на страницу завершения оплаты, это может быть любая ваша страница
|
||||
|
||||
//При необходимости можно изменить цвета виджета, подробные настройки см. в документации
|
||||
//customization: {
|
||||
//Настройка цветовой схемы, минимум один параметр, значения цветов в HEX
|
||||
//colors: {
|
||||
//Цвет акцентных элементов: кнопка Заплатить, выбранные переключатели, опции и текстовые поля
|
||||
//control_primary: '#00BF96', //Значение цвета в HEX
|
||||
|
||||
//Цвет платежной формы и ее элементов
|
||||
//background: '#F2F3F5' //Значение цвета в HEX
|
||||
//}
|
||||
//},
|
||||
error_callback: function(error) {
|
||||
console.log(error)
|
||||
}
|
||||
});
|
||||
|
||||
//Отображение платежной формы в контейнере
|
||||
checkout.render('payment-form');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user