Compare commits

...

2 Commits

Author SHA1 Message Date
ff29598ec5 Adding items to cart 2024-05-02 17:02:43 +03:00
727c04d368 AppBar and Header components 2024-05-02 15:04:05 +03:00
11 changed files with 490 additions and 130 deletions

View 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: Colors.white,
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);
}

View 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),
],
),
);
}
}

View File

@@ -37,16 +37,6 @@ class ProductCard extends StatelessWidget {
Text('\$$price', style: Theme.of(context).textTheme.titleSmall), Text('\$$price', style: Theme.of(context).textTheme.titleSmall),
], ],
), ),
// child: Column(
// 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),
// ],
// ),
), ),
), ),
); );

View File

@@ -3,7 +3,8 @@ import 'dart:js_interop' as js;
import 'dart:js_interop_unsafe' as js_util; import 'dart:js_interop_unsafe' as js_util;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/card.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/basket.dart';
import 'package:gymlink_module_web/pages/detail.dart'; import 'package:gymlink_module_web/pages/detail.dart';
import 'package:gymlink_module_web/pages/order_history.dart'; import 'package:gymlink_module_web/pages/order_history.dart';
@@ -122,29 +123,7 @@ class _MainPageState extends State<MainPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: widget.isLoading appBar: widget.isLoading ? null : const GymLinkAppBar(),
? null
: AppBar(
backgroundColor: Colors.white,
elevation: 0,
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,
),
body: widget.isLoading body: widget.isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
@@ -168,7 +147,9 @@ class _MainPageState extends State<MainPage> {
onPressed: _goToPage, onPressed: _goToPage,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 0), vertical: 8,
horizontal: 0,
),
minimumSize: minimumSize:
const Size(50, kMinInteractiveDimension), const Size(50, kMinInteractiveDimension),
backgroundColor: backgroundColor:

View File

@@ -1,21 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/app_bar.dart';
import 'package:gymlink_module_web/components/heading.dart';
class BasketPage extends StatelessWidget { //TODO: Вывод корзины из shared_prefs
const BasketPage({ class BasketPage extends StatefulWidget {
super.key, const BasketPage({super.key});
});
@override
State<BasketPage> createState() => _BasketPageState();
}
class _BasketPageState extends State<BasketPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
appBar: AppBar( appBar: GymLinkAppBar(),
leading: IconButton( body: Column(
icon: const Icon(Icons.arrow_back), children: [
onPressed: () => Navigator.pop(context), GymLinkHeader(title: 'Корзина'),
), Center(
title: const Text('Корзина'),
),
body: const Center(
child: Text('Корзина'), child: Text('Корзина'),
)); ),
],
),
);
} }
} }

View File

@@ -1,31 +1,108 @@
import 'package:flutter/material.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/tools/prefs.dart';
//TODO: Сделать получение инфы через объект //TODO: Сделать получение инфы через объект
class DetailPage extends StatelessWidget { class DetailPage extends StatefulWidget {
final String name; final String name;
final String description; final String description;
final String price; final String price;
final String id; final String id;
final Image image; final Image image;
const DetailPage( const DetailPage({
{super.key, super.key,
required this.name, required this.name,
required this.description, required this.description,
required this.price, required this.price,
required this.id, required this.id,
required this.image}); 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) {
debugPrint(value.toString());
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;
}
});
},
),
Text('$quantity'),
IconButton(
icon: const Icon(Icons.add),
onPressed: () async {
await addItemToCart(widget.id);
setState(() {
quantity++;
});
},
),
],
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: const GymLinkAppBar(),
leading: IconButton( body: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
icon: const Icon(Icons.arrow_back), GymLinkHeader(title: '${widget.name} - ${widget.id}'),
onPressed: () => Navigator.pop(context), Expanded(
), child: SingleChildScrollView(
title: Text('$name - $id'), child: Padding(
),
body: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: SizedBox( child: SizedBox(
width: MediaQuery.sizeOf(context).width, width: MediaQuery.sizeOf(context).width,
@@ -33,9 +110,10 @@ class DetailPage extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
image, widget.image,
Padding( Padding(
padding: const EdgeInsetsDirectional.fromSTEB(0, 60, 60, 60), padding:
const EdgeInsetsDirectional.fromSTEB(0, 30, 60, 60),
child: SizedBox( child: SizedBox(
width: 340, width: 340,
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
@@ -46,10 +124,10 @@ class DetailPage extends StatelessWidget {
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: Padding( child: Padding(
padding: padding: const EdgeInsetsDirectional.fromSTEB(
const EdgeInsetsDirectional.fromSTEB(20, 15, 10, 15), 20, 15, 10, 15),
child: Text( child: Text(
description, widget.description,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
), ),
), ),
@@ -59,28 +137,16 @@ class DetailPage extends StatelessWidget {
Align( Align(
alignment: const AlignmentDirectional(0, -1), alignment: const AlignmentDirectional(0, -1),
child: Padding( child: Padding(
padding: const EdgeInsetsDirectional.fromSTEB(0, 60, 0, 0), padding:
const EdgeInsetsDirectional.fromSTEB(0, 60, 0, 0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
'Стоимость $price', 'Стоимость ${widget.price}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
), ),
ElevatedButton( _buildButton()
onPressed: () => {},
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('Добавить в корзину'),
)
], ],
), ),
), ),
@@ -89,6 +155,9 @@ class DetailPage extends StatelessWidget {
), ),
), ),
), ),
),
),
]),
); );
} }
} }

View File

@@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/app_bar.dart';
import 'package:gymlink_module_web/components/heading.dart';
class HistoryPage extends StatelessWidget { class HistoryPage extends StatelessWidget {
const HistoryPage({ const HistoryPage({
@@ -6,16 +8,14 @@ class HistoryPage extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return const Scaffold(
appBar: AppBar( appBar: GymLinkAppBar(),
leading: IconButton( body: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
icon: const Icon(Icons.arrow_back), GymLinkHeader(title: 'История заказов'),
onPressed: () => Navigator.pop(context), Center(
),
title: const Text('История заказов'),
),
body: const Center(
child: Text('История заказов'), child: Text('История заказов'),
)); )
]),
);
} }
} }

42
lib/tools/prefs.dart Normal file
View File

@@ -0,0 +1,42 @@
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));
}

View File

@@ -5,8 +5,10 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
} }

View File

@@ -57,6 +57,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" 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: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -75,6 +91,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -139,6 +160,102 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -192,6 +309,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" 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: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -208,5 +389,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" 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: sdks:
dart: ">=3.3.3 <4.0.0" dart: ">=3.3.3 <4.0.0"
flutter: ">=3.19.0"

View File

@@ -36,6 +36,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
url_launcher: ^6.2.6 url_launcher: ^6.2.6
shared_preferences: ^2.2.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: