Compare commits

..

5 Commits

Author SHA1 Message Date
0a491ca34b Package rename 2024-05-01 20:36:40 +03:00
16d0ddca78 Goods details page 2024-05-01 20:17:20 +03:00
f941b26224 Product card on main screen 2024-05-01 16:44:18 +03:00
c6520041a6 Search btn and borders 2024-04-30 16:16:13 +03:00
7f0cef4b23 Created screens 2024-04-30 15:58:53 +03:00
16 changed files with 437 additions and 155 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/product.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

54
lib/components/card.dart Normal file
View File

@@ -0,0 +1,54 @@
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: const Color(0xFFF2F3F9),
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),
],
),
// 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,13 +3,17 @@ import 'dart:js_interop' as js;
import 'dart:js_interop_unsafe' as js_util;
import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/card.dart';
import 'package:gymlink_module_web/pages/basket.dart';
import 'package:gymlink_module_web/pages/detail.dart';
import 'package:gymlink_module_web/pages/order_history.dart';
import 'package:gymlink_module_web/theme.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(const MyApp());
}
enum DemoScreen { counter, textField }
class MyApp extends StatefulWidget {
const MyApp({super.key});
@@ -18,11 +22,47 @@ class MyApp extends StatefulWidget {
State<MyApp> createState() => _MyAppState();
}
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": "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"
}
];
@js.JSExport()
class _MyAppState extends State<MyApp> {
final _streanController = StreamController<void>.broadcast();
DemoScreen _currentDemoScreen = DemoScreen.counter;
int _counterScreenCount = 0;
final _streamController = StreamController<void>.broadcast();
bool _isLoading = true;
@override
@@ -35,39 +75,17 @@ class _MyAppState extends State<MyApp> {
@override
void dispose() {
_streanController.close();
_streamController.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),
),
title: 'GymLink Module',
theme: myTheme,
debugShowCheckedModeBanner: false,
home: demoScreenRouter(_currentDemoScreen),
home: MainPage(isLoading: _isLoading),
);
}
@@ -79,60 +97,63 @@ class _MyAppState extends State<MyApp> {
});
}
}
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;
class MainPage extends StatefulWidget {
final bool isLoading;
const CounterDemo(
{super.key,
required this.title,
required this.numToDisplay,
required this.incrementHandler,
required this.isLoading});
const MainPage({
super.key,
required this.isLoading,
});
@override
State<CounterDemo> createState() => _CounterDemoState();
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';
}
}
class _CounterDemoState extends State<CounterDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.isLoading
? 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
? 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(
@@ -141,86 +162,113 @@ class _CounterDemoState extends State<CounterDemo> {
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
suffixIcon: const Icon(
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.blue,
color: Colors.white,
size: 24,
),
),
),
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {},
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BasketPage(),
));
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
minimumSize: const Size(40, kMinInteractiveDimension),
backgroundColor: Theme.of(context).primaryColor,
shape: const CircleBorder(
side: BorderSide(
color: Colors.blue,
width: 2,
color: Colors.black,
width: 1,
),
),
),
child: const Icon(
Icons.shopping_basket,
color: Colors.white,
size: 36,
size: 24,
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {},
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const HistoryPage(),
));
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
minimumSize: const Size(40, kMinInteractiveDimension),
backgroundColor: Theme.of(context).primaryColor,
shape: const CircleBorder(
side: BorderSide(
color: Colors.blue,
width: 2,
color: Colors.black,
width: 1,
),
),
),
child: const Icon(
Icons.history,
color: Colors.white,
size: 36,
size: 24,
),
),
],
),
),
body: widget.isLoading
? const Center(child: CircularProgressIndicator())
: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Здесь будут товары',
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),
),
),
),
);
},
),
),
],
),
),
);
}
}
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()),
),
),
),
);
}
}

21
lib/pages/basket.dart Normal file
View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class BasketPage extends StatelessWidget {
const BasketPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: const Text('Корзина'),
),
body: const Center(
child: Text('Корзина'),
));
}
}

94
lib/pages/detail.dart Normal file
View File

@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
//TODO: Сделать получение инфы через объект
class DetailPage extends StatelessWidget {
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
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: Text('$name - $id'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: SizedBox(
width: MediaQuery.sizeOf(context).width,
height: MediaQuery.sizeOf(context).height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
image,
Padding(
padding: const EdgeInsetsDirectional.fromSTEB(0, 60, 60, 60),
child: SizedBox(
width: 340,
height: MediaQuery.sizeOf(context).height,
child: Card(
elevation: 4,
color: const Color(0xFFF2F3F9),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding:
const EdgeInsetsDirectional.fromSTEB(20, 15, 10, 15),
child: Text(
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(
'Стоимость $price',
style: Theme.of(context).textTheme.bodyLarge,
),
ElevatedButton(
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('Добавить в корзину'),
)
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class HistoryPage extends StatelessWidget {
const HistoryPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: const Text('История заказов'),
),
body: const Center(
child: Text('История заказов'),
));
}
}

26
lib/theme.dart Normal file
View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
final ThemeData myTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: getMaterialColor(const Color(0x007d85ff))));
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);
}

View File

@@ -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);
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

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

View File

@@ -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,7 @@ 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
dev_dependencies:
flutter_test:
@@ -57,6 +58,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:

View File

@@ -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;

View File

@@ -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"));
}

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST