Compare commits
16 Commits
master
...
4853f61da2
| Author | SHA1 | Date | |
|---|---|---|---|
| 4853f61da2 | |||
| 9d92dfd145 | |||
| 4b16b74d15 | |||
| fecc388e1c | |||
| e4628e977f | |||
| 4bbe7fbc0b | |||
| 5c3da0964a | |||
| 26f822e83a | |||
| 8805b2a9a0 | |||
| ff29598ec5 | |||
| 727c04d368 | |||
| 0a491ca34b | |||
| 16d0ddca78 | |||
| f941b26224 | |||
| c6520041a6 | |||
| 7f0cef4b23 |
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/product.png
Normal file
BIN
assets/product.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 455 KiB |
36
lib/components/app_bar.dart
Normal file
36
lib/components/app_bar.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GymLinkAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
const GymLinkAppBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
shadowColor: null,
|
||||
automaticallyImplyLeading: false,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 4,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Image.asset('logo.png', width: 24, height: 24),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'Powered by GymLink',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
toolbarHeight: 30,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(30);
|
||||
}
|
||||
87
lib/components/basket_item_card.dart
Normal file
87
lib/components/basket_item_card.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BasketItemCard extends StatelessWidget {
|
||||
final String name;
|
||||
final String price;
|
||||
final String id;
|
||||
final Image image;
|
||||
final String quantity;
|
||||
final VoidCallback onTapPlus;
|
||||
final VoidCallback onTapMinus;
|
||||
|
||||
const BasketItemCard({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.price,
|
||||
required this.id,
|
||||
required this.image,
|
||||
required this.onTapPlus,
|
||||
required this.quantity,
|
||||
required this.onTapMinus,
|
||||
});
|
||||
|
||||
@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: 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'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/components/heading.dart
Normal file
26
lib/components/heading.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GymLinkHeader extends StatelessWidget {
|
||||
final String title;
|
||||
const GymLinkHeader({super.key, required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back)),
|
||||
Text(title, style: Theme.of(context).textTheme.titleLarge),
|
||||
],
|
||||
),
|
||||
const Divider(thickness: 1, height: 0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
79
lib/components/history_item_card.dart
Normal file
79
lib/components/history_item_card.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.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 Image image;
|
||||
final OrderStatus status;
|
||||
|
||||
const HistoryItemCard({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.cost,
|
||||
required this.date,
|
||||
required this.status,
|
||||
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: 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.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**'),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
lib/components/item_card.dart
Normal file
50
lib/components/item_card.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProductCard extends StatelessWidget {
|
||||
final Image imagePath;
|
||||
final String name;
|
||||
final String price;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const ProductCard({
|
||||
super.key,
|
||||
required this.imagePath,
|
||||
required this.name,
|
||||
required this.price,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 200),
|
||||
child: Card(
|
||||
elevation: 3,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
imagePath,
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'\$$price',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
217
lib/main.dart
217
lib/main.dart
@@ -1,226 +1,13 @@
|
||||
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/states/web.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
enum DemoScreen { counter, textField }
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
class _MyAppState extends State<MyApp> {
|
||||
final _streanController = StreamController<void>.broadcast();
|
||||
DemoScreen _currentDemoScreen = DemoScreen.counter;
|
||||
int _counterScreenCount = 0;
|
||||
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() {
|
||||
_streanController.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void increment() {
|
||||
if (_currentDemoScreen == DemoScreen.counter) {
|
||||
setState(() {
|
||||
_counterScreenCount++;
|
||||
_streanController.add(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void addHandler(void Function() handler) {
|
||||
_streanController.stream.listen((event) {
|
||||
handler();
|
||||
});
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
int get count => _counterScreenCount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Aboba app',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: demoScreenRouter(_currentDemoScreen),
|
||||
);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void onTokenReceived(String token) {
|
||||
if (token == 'token123') {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget demoScreenRouter(DemoScreen which) {
|
||||
switch (which) {
|
||||
case DemoScreen.counter:
|
||||
return CounterDemo(
|
||||
title: 'Counter',
|
||||
numToDisplay: _counterScreenCount,
|
||||
incrementHandler: increment,
|
||||
isLoading: _isLoading);
|
||||
case DemoScreen.textField:
|
||||
return const TextFieldDemo(title: 'Nasdfs');
|
||||
}
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void changeDemoScreenTo(String screenString) {
|
||||
setState(() {
|
||||
switch (screenString) {
|
||||
case 'counter':
|
||||
_currentDemoScreen = DemoScreen.counter;
|
||||
break;
|
||||
case 'textField':
|
||||
_currentDemoScreen = DemoScreen.textField;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CounterDemo extends StatefulWidget {
|
||||
final String title;
|
||||
final int numToDisplay;
|
||||
final VoidCallback incrementHandler;
|
||||
final bool isLoading;
|
||||
|
||||
const CounterDemo(
|
||||
{super.key,
|
||||
required this.title,
|
||||
required this.numToDisplay,
|
||||
required this.incrementHandler,
|
||||
required this.isLoading});
|
||||
|
||||
@override
|
||||
State<CounterDemo> createState() => _CounterDemoState();
|
||||
}
|
||||
|
||||
class _CounterDemoState extends State<CounterDemo> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: widget.isLoading
|
||||
? null
|
||||
: AppBar(
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
suffixIcon: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.shopping_basket,
|
||||
color: Colors.white,
|
||||
size: 36,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(0),
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: Colors.blue,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.history,
|
||||
color: Colors.white,
|
||||
size: 36,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: widget.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Здесь будут товары',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextFieldDemo extends StatelessWidget {
|
||||
const TextFieldDemo({super.key, required this.title});
|
||||
final String title;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
body: const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.0),
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(border: OutlineInputBorder()),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
State<MyApp> createState() => MyAppStateWeb();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
282
lib/pages/basket.dart
Normal file
282
lib/pages/basket.dart
Normal file
@@ -0,0 +1,282 @@
|
||||
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/tools/prefs.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 BasketPage extends StatefulWidget {
|
||||
const BasketPage({super.key});
|
||||
|
||||
@override
|
||||
State<BasketPage> createState() => _BasketPageState();
|
||||
}
|
||||
|
||||
class _BasketPageState extends State<BasketPage> {
|
||||
List<Map<String, dynamic>> cartItems = [];
|
||||
int totalPrice = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCart().then((value) {
|
||||
setState(() {
|
||||
cartItems = value.map((element) {
|
||||
final item = cart.firstWhere((e) => e['id'] == element['id']);
|
||||
return {...item, 'count': element['count'] as int};
|
||||
}).toList();
|
||||
totalPrice = cartItems.fold(
|
||||
0,
|
||||
(sum, item) =>
|
||||
sum + int.parse(item['price']) * item['count'] as int);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void removeItem(String id) async {
|
||||
final item = cartItems.firstWhere((element) => element['id'] == id);
|
||||
bool toDelete = false;
|
||||
setState(() {
|
||||
if (item['count'] > 1) {
|
||||
item['count']--;
|
||||
cartItems[cartItems.indexOf(item)]['count'] = item['count'];
|
||||
} else {
|
||||
toDelete = true;
|
||||
}
|
||||
totalPrice = cartItems.fold(0,
|
||||
(sum, item) => sum + int.parse(item['price']) * item['count'] as int);
|
||||
});
|
||||
if (toDelete) {
|
||||
await _deleteItemAlert(id, item['name']);
|
||||
} else {
|
||||
await removeItemFromCart(id);
|
||||
}
|
||||
}
|
||||
|
||||
void addItem(String id) async {
|
||||
setState(() {
|
||||
final item = cartItems.firstWhere((element) => element['id'] == id,
|
||||
orElse: () => {
|
||||
...cart.firstWhere((element) => element['id'] == id),
|
||||
'count': 0
|
||||
});
|
||||
item['count']++;
|
||||
cartItems[cartItems.indexOf(item)]['count'] = item['count'];
|
||||
totalPrice = cartItems.fold(0,
|
||||
(sum, item) => sum + int.parse(item['price']) * item['count'] as int);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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 = [];
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
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,
|
||||
),
|
||||
onTapPlus: () => addItem(item['id'].toString()),
|
||||
onTapMinus: () =>
|
||||
removeItem(item['id'].toString()),
|
||||
quantity: item['count'].toString(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(
|
||||
horizontal: 10, vertical: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Итого: $totalPrice',
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {},
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
175
lib/pages/detail.dart
Normal file
175
lib/pages/detail.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
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/tools/prefs.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
|
||||
State<DetailPage> createState() => _DetailPageState();
|
||||
}
|
||||
|
||||
class _DetailPageState extends State<DetailPage> {
|
||||
bool isInCart = false;
|
||||
int quantity = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getCart().then((value) {
|
||||
setState(() {
|
||||
isInCart = value.any((element) => element['id'] == widget.id);
|
||||
if (isInCart) {
|
||||
quantity = value
|
||||
.firstWhere((element) => element['id'] == widget.id)['count'];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildButton() {
|
||||
if (!isInCart) {
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
await addItemToCart(widget.id);
|
||||
setState(() {
|
||||
isInCart = true;
|
||||
quantity = 1;
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(50)),
|
||||
),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsetsDirectional.fromSTEB(34, 10, 34, 10)),
|
||||
child: const Text('Добавить в корзину'),
|
||||
);
|
||||
} else {
|
||||
return 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;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text('$quantity'),
|
||||
const SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
await addItemToCart(widget.id);
|
||||
setState(() {
|
||||
quantity++;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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: Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
_buildButton()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
207
lib/pages/main.dart
Normal file
207
lib/pages/main.dart
Normal file
@@ -0,0 +1,207 @@
|
||||
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:url_launcher/url_launcher.dart';
|
||||
|
||||
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"
|
||||
}
|
||||
];
|
||||
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
80
lib/pages/order_history.dart
Normal file
80
lib/pages/order_history.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
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';
|
||||
|
||||
List<Map<String, String>> orders = [
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "120",
|
||||
"id": "34fa3126-bfaf-5dec-8f4a-b246c097ef73",
|
||||
"date": "11.09.2001"
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "150",
|
||||
"id": "34a26e82-7656-5e98-a44a-c2d01d0b1ad1123",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "250",
|
||||
"id": "4fb204b7-3f9e-52a2-bed1-415c00a31a37123",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "300",
|
||||
"id": "09b2f5bb-683e-5c39-ae89-b8e152fa8bcf123",
|
||||
"date": "11.09.2001",
|
||||
},
|
||||
{
|
||||
"image": "product.png",
|
||||
"price": "100",
|
||||
"id": "cd1b6817-db94-5394-be1d-af88af79749f123",
|
||||
"date": "11.09.2001",
|
||||
}
|
||||
];
|
||||
|
||||
class HistoryPage extends StatelessWidget {
|
||||
const HistoryPage({
|
||||
super.key,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const GymLinkAppBar(),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const GymLinkHeader(title: 'История заказов'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: orders.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = orders[index];
|
||||
return HistoryItemCard(
|
||||
id: item['id']!,
|
||||
cost: item['price']!,
|
||||
date: item['date']!,
|
||||
image: Image.asset(
|
||||
item['image']!,
|
||||
width: 50,
|
||||
),
|
||||
status: OrderStatus.completed,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
35
lib/states/mobile.dart
Normal file
35
lib/states/mobile.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
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/theme.dart';
|
||||
|
||||
class MyAppStateMobile extends State<MyApp> {
|
||||
bool _isLoading = true;
|
||||
ThemeData theme = myTheme;
|
||||
bool black_theme = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: MainPage(isLoading: _isLoading),
|
||||
);
|
||||
}
|
||||
|
||||
void onTokenReceived(String token) {
|
||||
if (token == 'token123') {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void changeColor(int color) {
|
||||
setState(() {
|
||||
black_theme = !black_theme; //FIXME: TEMPORARY
|
||||
theme = getThemeData(Color(color), black_theme);
|
||||
});
|
||||
}
|
||||
}
|
||||
57
lib/states/web.dart
Normal file
57
lib/states/web.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
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/theme.dart';
|
||||
|
||||
@js.JSExport()
|
||||
class MyAppStateWeb extends State<MyApp> {
|
||||
final _streamController = StreamController<void>.broadcast();
|
||||
bool _isLoading = true;
|
||||
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) {
|
||||
return MaterialApp(
|
||||
title: 'GymLink Module',
|
||||
theme: theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: MainPage(isLoading: _isLoading),
|
||||
);
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void onTokenReceived(String token) {
|
||||
if (token == 'token123') {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@js.JSExport()
|
||||
void changeColor(int color) {
|
||||
setState(() {
|
||||
black_theme = !black_theme; //FIXME: TEMPORARY
|
||||
theme = getThemeData(Color(color), black_theme);
|
||||
});
|
||||
}
|
||||
}
|
||||
38
lib/theme.dart
Normal file
38
lib/theme.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
MaterialColor getMaterialColor(Color color) {
|
||||
final int red = color.red;
|
||||
final int green = color.green;
|
||||
final int blue = color.blue;
|
||||
|
||||
final Map<int, Color> shades = {
|
||||
50: Color.fromRGBO(red, green, blue, .1),
|
||||
100: Color.fromRGBO(red, green, blue, .2),
|
||||
200: Color.fromRGBO(red, green, blue, .3),
|
||||
300: Color.fromRGBO(red, green, blue, .4),
|
||||
400: Color.fromRGBO(red, green, blue, .5),
|
||||
500: Color.fromRGBO(red, green, blue, .6),
|
||||
600: Color.fromRGBO(red, green, blue, .7),
|
||||
700: Color.fromRGBO(red, green, blue, .8),
|
||||
800: Color.fromRGBO(red, green, blue, .9),
|
||||
900: Color.fromRGBO(red, green, blue, 1),
|
||||
};
|
||||
|
||||
return MaterialColor(color.value, shades);
|
||||
}
|
||||
47
lib/tools/prefs.dart
Normal file
47
lib/tools/prefs.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
Future<void> addItemToCart(String id) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String cartString = prefs.getString('cart') ?? "[]";
|
||||
List<Map<String, dynamic>> cart =
|
||||
List<Map<String, dynamic>>.from(jsonDecode(cartString) as List<dynamic>);
|
||||
final index = cart.indexWhere((element) => element['id'] == id);
|
||||
if (index == -1) {
|
||||
cart.add({'id': id, 'count': 1});
|
||||
} else {
|
||||
cart[index]['count'] = cart[index]['count']! + 1;
|
||||
}
|
||||
prefs.setString('cart', jsonEncode(cart));
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getCart() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String cartString = prefs.getString('cart') ?? "[]";
|
||||
List<Map<String, dynamic>> cart =
|
||||
List<Map<String, dynamic>>.from(jsonDecode(cartString) as List<dynamic>);
|
||||
return cart;
|
||||
}
|
||||
|
||||
Future<void> removeItemFromCart(String id) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
String cartString = prefs.getString('cart') ?? "[]";
|
||||
List<Map<String, dynamic>> cart =
|
||||
List<Map<String, dynamic>>.from(jsonDecode(cartString) as List<dynamic>);
|
||||
cart.removeWhere((element) => element['id'] == id && element['count'] == 1);
|
||||
for (final item in cart) {
|
||||
if (item['id'] == id) {
|
||||
item['count'] = item['count']! - 1;
|
||||
if (item['count'] == 0) {
|
||||
cart.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
prefs.setString('cart', jsonEncode(cart));
|
||||
}
|
||||
|
||||
Future<void> clearCart() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('cart', "[]");
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
||||
206
pubspec.lock
206
pubspec.lock
@@ -57,6 +57,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -75,6 +91,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -139,6 +160,102 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -192,6 +309,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.6"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.5"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -208,5 +389,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
sdks:
|
||||
dart: ">=3.3.3 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
11
pubspec.yaml
11
pubspec.yaml
@@ -1,5 +1,5 @@
|
||||
name: flutter_application_1
|
||||
description: "A new Flutter project."
|
||||
name: gymlink_module_web
|
||||
description: "GymLink Flutter Web Module."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
@@ -35,6 +35,10 @@ dependencies:
|
||||
# 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
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -57,6 +61,9 @@ flutter:
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/logo.png
|
||||
- assets/product.png
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#flutter_target {
|
||||
border: 1px solid #aaa;
|
||||
width: 320px;
|
||||
height: 480px;
|
||||
width: 80vw;
|
||||
height: 60vh;
|
||||
border-radius: 0px;
|
||||
transition: all 150ms ease-in;
|
||||
align-self: center;
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
</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>
|
||||
|
||||
@@ -12,5 +12,20 @@
|
||||
appState.onTokenReceived('token123');
|
||||
})
|
||||
|
||||
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 numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor)
|
||||
})
|
||||
|
||||
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 numColor = parseInt(hexColor, 16);
|
||||
appState.changeColor(numColor)
|
||||
})
|
||||
}
|
||||
}());
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user