mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 08:44:06 +02:00
bottom navigation bar and product page
This commit is contained in:
parent
f5d9507427
commit
66a8f6c7aa
@ -21,6 +21,7 @@ linter:
|
|||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
|
prefer_const_constructors: true
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
BIN
mobile/assets/Frilands Øko Supermælk.png
Normal file
BIN
mobile/assets/Frilands Øko Supermælk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 625 KiB |
BIN
mobile/assets/Letmælk.png
Normal file
BIN
mobile/assets/Letmælk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
mobile/assets/Minimælk.png
Normal file
BIN
mobile/assets/Minimælk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
3
mobile/devtools_options.yaml
Normal file
3
mobile/devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
114
mobile/lib/all_products_page.dart
Normal file
114
mobile/lib/all_products_page.dart
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mobile/product.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'product_page.dart';
|
||||||
|
|
||||||
|
class ProductListItem extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
|
final int price;
|
||||||
|
final String imagePath;
|
||||||
|
final ProductPage productPage;
|
||||||
|
const ProductListItem(
|
||||||
|
{super.key,
|
||||||
|
required this.name,
|
||||||
|
required this.price,
|
||||||
|
required this.imagePath,
|
||||||
|
required this.productPage});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFFFFFF),
|
||||||
|
border: Border.all(color: const Color(0xFF666666)),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10))),
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStateProperty.all(Colors.transparent),
|
||||||
|
elevation: WidgetStateProperty.all(0),
|
||||||
|
shape: WidgetStateProperty.all(const RoundedRectangleBorder()),
|
||||||
|
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||||
|
splashFactory: NoSplash.splashFactory),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(MaterialPageRoute(builder: (context) => productPage));
|
||||||
|
},
|
||||||
|
child: Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(10, 10, 0, 10),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 20, color: Colors.black),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${price.toString()} kr",
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 16, color: Colors.black),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topRight: Radius.circular(10),
|
||||||
|
bottomRight: Radius.circular(10)),
|
||||||
|
child: Image(
|
||||||
|
image: AssetImage(imagePath),
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
width: 100,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AllProductsPage extends StatelessWidget {
|
||||||
|
const AllProductsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(children: [
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
BackButton(),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: Text("Search"),
|
||||||
|
contentPadding: EdgeInsets.only(top: 20))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Consumer<ProductRepo>(builder: (_, productRepo, __) {
|
||||||
|
final products = productRepo.allProducts();
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (_, idx) => ProductListItem(
|
||||||
|
name: products[idx].name,
|
||||||
|
price: products[idx].price,
|
||||||
|
imagePath: "assets/${products[idx].name}.png",
|
||||||
|
productPage: ProductPage(product: products[idx]),
|
||||||
|
),
|
||||||
|
itemCount: products.length,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
mobile/lib/cart_page.dart
Normal file
10
mobile/lib/cart_page.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CartPage extends StatelessWidget {
|
||||||
|
const CartPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold();
|
||||||
|
}
|
||||||
|
}
|
@ -1,98 +1,67 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'global_components.dart';
|
import 'package:mobile/all_products_page.dart';
|
||||||
|
import 'package:mobile/cart_page.dart';
|
||||||
|
import 'package:mobile/receipts_page.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ProductListItem extends StatelessWidget {
|
class BottomNavigationBarProvider extends ChangeNotifier {
|
||||||
final String name;
|
int currentIndex = 0;
|
||||||
final int price;
|
|
||||||
final String imagePath;
|
|
||||||
const ProductListItem(
|
|
||||||
{super.key,
|
|
||||||
required this.name,
|
|
||||||
required this.price,
|
|
||||||
required this.imagePath});
|
|
||||||
|
|
||||||
@override
|
void setIndex(int index) {
|
||||||
Widget build(BuildContext context) {
|
currentIndex = index;
|
||||||
return Container(
|
notifyListeners();
|
||||||
margin: EdgeInsets.all(10),
|
|
||||||
height: 100,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Color(0xFFFFFFFF),
|
|
||||||
border: Border.all(),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
|
||||||
child: ElevatedButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.all(Colors.transparent),
|
|
||||||
elevation: WidgetStateProperty.all(0),
|
|
||||||
shape: WidgetStateProperty.all(RoundedRectangleBorder()),
|
|
||||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
|
||||||
splashFactory: NoSplash.splashFactory),
|
|
||||||
onPressed: () {},
|
|
||||||
child: Expanded(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.fromLTRB(10, 10, 0, 10),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
name,
|
|
||||||
style: TextStyle(fontSize: 24, color: Colors.black),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"${price.toString()} kr",
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
bottomRight: Radius.circular(10)),
|
|
||||||
child:
|
|
||||||
Image(image: AssetImage(imagePath), fit: BoxFit.contain))
|
|
||||||
],
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dashboard extends StatelessWidget {
|
class Dashboard extends StatelessWidget {
|
||||||
const Dashboard({super.key});
|
final List<StatelessWidget> pages = [
|
||||||
|
const AllProductsPage(),
|
||||||
|
const CartPage(),
|
||||||
|
const ReceiptsPage(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Dashboard({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final pageIndexProvider = Provider.of<BottomNavigationBarProvider>(context);
|
||||||
|
int currentIndex = pageIndexProvider.currentIndex;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Row(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onTap: (index) => pageIndexProvider.setIndex(index),
|
||||||
children: [
|
currentIndex: currentIndex,
|
||||||
Expanded(
|
items: <BottomNavigationBarItem>[
|
||||||
child: Column(children: [
|
BottomNavigationBarItem(
|
||||||
TextField(
|
icon: Icon(currentIndex == 0 ? Icons.home : Icons.home_outlined),
|
||||||
decoration: InputDecoration(
|
label: "Home"),
|
||||||
label: Text("Search"),
|
BottomNavigationBarItem(
|
||||||
contentPadding: EdgeInsets.only(top: 20))),
|
icon: Icon(currentIndex == 1
|
||||||
Expanded(
|
? Icons.shopping_cart
|
||||||
child: ListView(
|
: Icons.shopping_cart_outlined),
|
||||||
children: [
|
label: "Cart"),
|
||||||
ProductListItem(
|
BottomNavigationBarItem(
|
||||||
name: "idk",
|
icon: Icon(currentIndex == 2
|
||||||
price: 12,
|
? Icons.receipt_long
|
||||||
imagePath: "assets/boykisser.png",
|
: Icons.receipt_long_outlined),
|
||||||
),
|
label: "Receipts")
|
||||||
ProductListItem(
|
],
|
||||||
name: "idk",
|
),
|
||||||
price: 12,
|
body: pages[currentIndex],
|
||||||
imagePath: "assets/boykisser.png",
|
);
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Consumer<ProductRepo>(builder: (_, productRepo, __) {
|
||||||
|
// final products = productRepo.allProducts();
|
||||||
|
// return ListView.builder(
|
||||||
|
// shrinkWrap: true,
|
||||||
|
// itemBuilder: (_, idx) => ProductListItem(
|
||||||
|
// name: products[idx].name,
|
||||||
|
// price: products[idx].price,
|
||||||
|
// imagePath: "assets/${products[idx].name}.png",
|
||||||
|
// productPage: ProductPage(product: products[idx]),
|
||||||
|
// ),
|
||||||
|
// itemCount: products.length,
|
||||||
|
// );
|
||||||
|
// })
|
||||||
|
@ -22,8 +22,8 @@ class LogInPage extends StatelessWidget {
|
|||||||
label: "Password", placeholderText: "*********", obscure: true),
|
label: "Password", placeholderText: "*********", obscure: true),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).push(MaterialPageRoute(
|
Navigator.of(context).push(
|
||||||
builder: (context) => const Dashboard()))
|
MaterialPageRoute(builder: (context) => Dashboard()))
|
||||||
},
|
},
|
||||||
child: const Text("Log ind"))
|
child: const Text("Log ind"))
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mobile/dashboard.dart';
|
||||||
|
import 'package:mobile/product.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'landing_page.dart';
|
import 'landing_page.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -10,14 +13,20 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MultiProvider(
|
||||||
title: 'Fresh Plaza',
|
providers: [
|
||||||
theme: ThemeData(
|
ChangeNotifierProvider(create: (_) => ProductRepo()),
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
ChangeNotifierProvider(create: (_) => BottomNavigationBarProvider())
|
||||||
scaffoldBackgroundColor: const Color(0xECF6F0FF),
|
],
|
||||||
useMaterial3: true,
|
child: MaterialApp(
|
||||||
|
title: 'Fresh Plaza',
|
||||||
|
theme: ThemeData(
|
||||||
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||||
|
scaffoldBackgroundColor: const Color(0xECF6F0FF),
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
home: const MyHomePage(title: 'Fresh Plaza'),
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'Fresh Plaza'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
mobile/lib/product.dart
Normal file
43
mobile/lib/product.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ProductRepo extends ChangeNotifier {
|
||||||
|
final List<Product> _products = [
|
||||||
|
Product(
|
||||||
|
id: 0,
|
||||||
|
name: "Minimælk",
|
||||||
|
price: 12,
|
||||||
|
description: "Konventionel minimælk med fedtprocent på 0,4%"),
|
||||||
|
Product(
|
||||||
|
id: 1,
|
||||||
|
name: "Letmælk",
|
||||||
|
price: 13,
|
||||||
|
description: "Konventionel letmælk med fedtprocent på 1,5%"),
|
||||||
|
Product(
|
||||||
|
id: 2,
|
||||||
|
name: "Frilands Øko Supermælk",
|
||||||
|
price: 20,
|
||||||
|
description:
|
||||||
|
"Økologisk mælk af frilandskøer med fedtprocent på 3,5%. Ikke homogeniseret eller pasteuriseret. Skaber store muskler og styrker knoglerne 💪")
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Product> allProducts() {
|
||||||
|
return _products;
|
||||||
|
}
|
||||||
|
|
||||||
|
void changePrice(int idx, int price) {
|
||||||
|
_products[idx].price = price;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
int price;
|
||||||
|
Product(
|
||||||
|
{required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.price,
|
||||||
|
required this.description});
|
||||||
|
}
|
83
mobile/lib/product_page.dart
Normal file
83
mobile/lib/product_page.dart
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mobile/global_components.dart';
|
||||||
|
import 'package:mobile/product.dart';
|
||||||
|
|
||||||
|
class ProductPage extends StatelessWidget {
|
||||||
|
final Product product;
|
||||||
|
|
||||||
|
const ProductPage({super.key, required this.product});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||||
|
color: const Color(0xFFFFFFFF),
|
||||||
|
border: Border.all(color: const Color(0xFF666666)),
|
||||||
|
),
|
||||||
|
child: Column(children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const BackButton(),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
product.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${product.price} kr",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Image(
|
||||||
|
image: AssetImage("assets/${product.name}.png"),
|
||||||
|
height: 250,
|
||||||
|
fit: BoxFit.fitHeight,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
product.name,
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${product.price} kr",
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||||
|
child: Text(product.description),
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () {}, child: const Text("Find i butik")),
|
||||||
|
PrimaryButton(
|
||||||
|
onPressed: () {},
|
||||||
|
child: const Text("Tilføj til indkøbskurv")),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
10
mobile/lib/receipts_page.dart
Normal file
10
mobile/lib/receipts_page.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ReceiptsPage extends StatelessWidget {
|
||||||
|
const ReceiptsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold();
|
||||||
|
}
|
||||||
|
}
|
@ -131,6 +131,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -139,6 +147,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
|
provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: provider
|
||||||
|
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -34,6 +34,7 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
|
provider: ^6.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user