Compare commits

...

3 Commits

Author SHA1 Message Date
4853f61da2 Mobile and web versions 2024-05-10 19:50:33 +03:00
9d92dfd145 Example theme change 2024-05-10 19:49:21 +03:00
4b16b74d15 Theme gen 2024-05-10 19:49:08 +03:00
11 changed files with 346 additions and 255 deletions

View File

@@ -6,7 +6,7 @@ class GymLinkAppBar extends StatelessWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(
backgroundColor: Colors.white, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
shadowColor: null, shadowColor: null,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
elevation: 0, elevation: 0,

View File

@@ -22,7 +22,7 @@ class ProductCard extends StatelessWidget {
constraints: const BoxConstraints(minHeight: 200), constraints: const BoxConstraints(minHeight: 200),
child: Card( child: Card(
elevation: 3, elevation: 3,
color: const Color(0xFFF2F3F9), color: Theme.of(context).scaffoldBackgroundColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),

View File

@@ -1,15 +1,5 @@
import 'dart:async';
import 'dart:js_interop' as js;
import 'dart:js_interop_unsafe' as js_util;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gymlink_module_web/components/app_bar.dart'; import 'package:gymlink_module_web/states/web.dart';
import 'package:gymlink_module_web/components/item_card.dart';
import 'package:gymlink_module_web/pages/basket.dart';
import 'package:gymlink_module_web/pages/detail.dart';
import 'package:gymlink_module_web/pages/order_history.dart';
import 'package:gymlink_module_web/theme.dart';
import 'package:url_launcher/url_launcher.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@@ -18,246 +8,6 @@ void main() {
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
// This widget is the root of your application.
@override @override
State<MyApp> createState() => _MyAppState(); State<MyApp> createState() => MyAppStateWeb();
}
const List<Map<String, String>> testData = [
{
"name": "Протеин",
"image": "product.png",
"price": "120",
"details": "Test details",
"id": "34fa3126-bfaf-5dec-8f4a-b246c097ef73"
},
{
"name": "Протеин",
"image": "product.png",
"price": "150",
"details":
"that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his that name factory say string eaten order harbor easier said tone now floor nest it comfortable such difficulty labor bridge fact market women badly chamber heading forest allow shirt possibly story strip elephant extra even joy lungs than low discussion barn rapidly evidence is stream crew let more sold bag river triangle court slept knowledge flat package research balloon station underline careful market better make curious secret boy poor captured creature harder public tool ring subject charge planet tone scientist piece page stone support bush way feathers summer describe back should said complex song giant his",
"id": "34a26e82-7656-5e98-a44a-c2d01d0b1ad1123"
},
{
"name": "Протеин",
"image": "product.png",
"price": "250",
"details": "Test details",
"id": "4fb204b7-3f9e-52a2-bed1-415c00a31a37123"
},
{
"name": "Протеин",
"image": "product.png",
"price": "300",
"details": "Test details",
"id": "09b2f5bb-683e-5c39-ae89-b8e152fa8bcf123"
},
{
"name": "Протеин",
"image": "product.png",
"price": "100",
"details": "Test details",
"id": "cd1b6817-db94-5394-be1d-af88af79749f123"
}
];
@js.JSExport()
class _MyAppState extends State<MyApp> {
final _streamController = StreamController<void>.broadcast();
bool _isLoading = true;
@override
void initState() {
super.initState();
final export = js.createJSInteropWrapper(this);
js.globalContext['_appState'] = export;
js.globalContext.callMethod('_stateSet'.toJS);
}
@override
void dispose() {
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GymLink Module',
theme: myTheme,
debugShowCheckedModeBanner: false,
home: MainPage(isLoading: _isLoading),
);
}
@js.JSExport()
void onTokenReceived(String token) {
if (token == 'token123') {
setState(() {
_isLoading = false;
});
}
}
}
class MainPage extends StatefulWidget {
final bool isLoading;
const MainPage({
super.key,
required this.isLoading,
});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Future<void> _goToPage() async {
final Uri url = Uri.parse('https://google.com');
if (!await launchUrl(url, webOnlyWindowName: '_blank')) {
throw 'Could not launch $url';
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.isLoading ? null : const GymLinkAppBar(),
body: widget.isLoading
? const Center(child: CircularProgressIndicator())
: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: 'Search',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8),
child: ElevatedButton(
onPressed: _goToPage,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 0,
),
minimumSize:
const Size(50, kMinInteractiveDimension),
backgroundColor:
Theme.of(context).primaryColor,
shape: const CircleBorder(),
),
child: const Icon(
Icons.search,
color: Colors.white,
size: 24,
),
),
),
),
),
),
const Spacer(
flex: 2,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const BasketPage(),
));
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
minimumSize: const Size(50, kMinInteractiveDimension),
backgroundColor: Theme.of(context).primaryColor,
shape: const CircleBorder(
side: BorderSide(
color: Colors.black,
width: 1,
),
),
),
child: const Icon(
Icons.shopping_basket,
color: Colors.white,
size: 24,
),
),
const SizedBox(
width: 8,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const HistoryPage(),
));
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(0),
minimumSize: const Size(50, kMinInteractiveDimension),
backgroundColor: Theme.of(context).primaryColor,
shape: const CircleBorder(
side: BorderSide(
color: Colors.black,
width: 1,
),
),
),
child: const Icon(
Icons.history,
color: Colors.white,
size: 24,
),
),
const SizedBox(
width: 10,
)
],
),
),
Expanded(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount:
(MediaQuery.sizeOf(context).width ~/ 250).floor(),
),
itemCount: testData.length,
itemBuilder: (context, index) {
final product = testData[index];
return ProductCard(
imagePath: Image.asset(
product['image']!,
width: 100,
),
name: product['name']!,
price: product['price']!,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DetailPage(
name: product['name']!,
description: product['details']!,
price: product['price']!,
id: product['id']!,
image: Image.asset(product['image']!, width: 300),
),
),
),
);
},
),
),
],
),
);
}
} }

13
lib/main_mobile.dart Normal file
View 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();
}

View File

@@ -124,7 +124,7 @@ class _DetailPageState extends State<DetailPage> {
), ),
child: Card( child: Card(
elevation: 4, elevation: 4,
color: const Color(0xFFF2F3F9), color: Theme.of(context).scaffoldBackgroundColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),

207
lib/pages/main.dart Normal file
View 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),
),
),
),
);
},
),
),
],
),
);
}
}

35
lib/states/mobile.dart Normal file
View 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
View 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);
});
}
}

View File

@@ -4,6 +4,18 @@ final ThemeData myTheme = ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: getMaterialColor(const Color(0x007d85ff)))); 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) { MaterialColor getMaterialColor(Color color) {
final int red = color.red; final int red = color.red;
final int green = color.green; final int green = color.green;

View File

@@ -42,6 +42,8 @@
</head> </head>
<body> <body>
<button id='token'>Token btn</button> <button id='token'>Token btn</button>
<button id='colorChangeBtnRed'>Color btn Red</button>
<button id='colorChangeBtnBlue'>Color btn Blue</button>
<section class='contents'> <section class='contents'>
<article> <article>
<div id="flutter_target"></div> <div id="flutter_target"></div>

View File

@@ -12,5 +12,20 @@
appState.onTokenReceived('token123'); 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)
})
} }
}()); }());