First draft of Onboarding page
This commit is contained in:
@@ -26,4 +26,4 @@
|
|||||||
<orderEntry type="library" name="Flutter for Android" level="project" />
|
<orderEntry type="library" name="Flutter for Android" level="project" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
19
lib/core/const/color_constants.dart
Normal file
19
lib/core/const/color_constants.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
class ColorConstants {
|
||||||
|
static const textColor = Color(0xFF1F2022);
|
||||||
|
static const primaryColor = Color(0xFF6358E1);
|
||||||
|
static const textBlack = Color(0xFF1F2022);
|
||||||
|
static const white = Color(0xFFFFFFFF);
|
||||||
|
static const grey = Color(0xFFB6BDC6);
|
||||||
|
static const loadingBlack = Color(0x80000000);
|
||||||
|
|
||||||
|
static const textFieldBackground = Color(0xFFFBFCFF);
|
||||||
|
static const textFieldBorder = Color (0xFFB9BBC5);
|
||||||
|
static const disabledColor = Color(0xFFE1E1E5);
|
||||||
|
static const errorColor = Color (0xFFF25252);
|
||||||
|
static const homeBackgroundColor = Color.fromRGBO(252, 252, 252, 1);
|
||||||
|
static const textGrey = Color(0xFF8F98A3);
|
||||||
|
static const cardioColor = Color(0xFFFCB74F);
|
||||||
|
static const armsColor = Color(0xFF5C9BA4);
|
||||||
|
}
|
||||||
25
lib/core/const/data_constants.dart
Normal file
25
lib/core/const/data_constants.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:tutorialtest/core/const/path_constants.dart';
|
||||||
|
import 'package:tutorialtest/core/const/text_constants.dart';
|
||||||
|
|
||||||
|
import '../../screens/onboarding/widget/onboarding_tile.dart';
|
||||||
|
|
||||||
|
class DataConstants {
|
||||||
|
// Onboarding
|
||||||
|
static final onboardingTiles = [
|
||||||
|
OnboardingTile(
|
||||||
|
title: TextConstants.onboarding1Title,
|
||||||
|
mainText: TextConstants.onboardingDescription,
|
||||||
|
imagePath: PathConstants.onboarding1
|
||||||
|
),
|
||||||
|
OnboardingTile(
|
||||||
|
title: TextConstants.onboardingTitle,
|
||||||
|
mainText: TextConstants.onboardingDescription,
|
||||||
|
imagePath: PathConstants.onboarding2
|
||||||
|
),
|
||||||
|
OnboardingTile(
|
||||||
|
title: TextConstants.onboarding3Title,
|
||||||
|
mainText: TextConstants.onboarding3Description,
|
||||||
|
imagePath: PathConstants.onboarding3
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
20
lib/core/const/path_constants.dart
Normal file
20
lib/core/const/path_constants.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
class PathConstants{
|
||||||
|
// Onboarding
|
||||||
|
static const String onboarding1 = 'assets/images/onboarding/onboarding.png';
|
||||||
|
static const String onboarding2 = 'assets/images/onboarding/onboarding_2.png';
|
||||||
|
static const String onboarding3 = 'assets/images/onboarding/onboarding_3.png';
|
||||||
|
// Auth
|
||||||
|
static const String eye = 'assets/images/auth/eye_icon.png';
|
||||||
|
// Tabbar
|
||||||
|
static const String home = 'assets/icons/home/home_icon.png';
|
||||||
|
static const String workouts = 'assets/icons/home/workouts_icon.png';
|
||||||
|
static const String settings = 'assets/icons/home/settings_icon.png';
|
||||||
|
// Home
|
||||||
|
static const String profile = 'assets/images/home/profile.png';
|
||||||
|
static const String finished = 'assets/images/home/finished.png';
|
||||||
|
static const String inProgress = 'assets/icons/home/inProgress.png';
|
||||||
|
static const String timeSent = 'assets/icons/home/time.png';
|
||||||
|
static const String cardio = 'assets/images/home/cardio.png';
|
||||||
|
static const String arms = 'assets/images/home/arms.png';
|
||||||
|
static const String progress = 'assets/icons/home/progress.png';
|
||||||
|
}
|
||||||
12
lib/core/const/text_constants.dart
Normal file
12
lib/core/const/text_constants.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
class TextConstants {
|
||||||
|
// Onboarding
|
||||||
|
static const String onboarding1Title = "Workout anywhere";
|
||||||
|
static const String onboardingTitle = "Learn techniques";
|
||||||
|
static const String onboarding3Title = "Stay strong & healthy";
|
||||||
|
static const String onboardingDescription =
|
||||||
|
"You can do your workout at home without any equipment, outside or at the gym.";
|
||||||
|
static const String onboarding2Description =
|
||||||
|
"Our workout programs are made by professionals.";
|
||||||
|
static const String onboarding3Description =
|
||||||
|
"We want you to fully enjoy the program and stay healthy and positive.";
|
||||||
|
}
|
||||||
0
lib/core/extensions/exceptions.dart
Normal file
0
lib/core/extensions/exceptions.dart
Normal file
0
lib/core/extensions/list_extension.dart
Normal file
0
lib/core/extensions/list_extension.dart
Normal file
0
lib/core/service/auth_service.dart
Normal file
0
lib/core/service/auth_service.dart
Normal file
0
lib/core/service/date_service.dart
Normal file
0
lib/core/service/date_service.dart
Normal file
0
lib/core/service/notification_service.dart
Normal file
0
lib/core/service/notification_service.dart
Normal file
0
lib/core/service/user_service.dart
Normal file
0
lib/core/service/user_service.dart
Normal file
0
lib/core/service/user_storage_service.dart
Normal file
0
lib/core/service/user_storage_service.dart
Normal file
0
lib/core/service/validation_service.dart
Normal file
0
lib/core/service/validation_service.dart
Normal file
@@ -1,29 +1,29 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:tutorialtest/screens/onboarding/page/onboarding_page.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
SystemChrome.setPreferredOrientations([
|
SystemChrome.setPreferredOrientations([
|
||||||
DeviceOrientation.portraitUp,
|
DeviceOrientation.portraitUp,
|
||||||
DeviceOrientation.portraitDown,
|
DeviceOrientation.portraitDown,
|
||||||
]);
|
]);
|
||||||
await Firebase.initializeApp();
|
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isLoggedIn = FirebaseAuth.instance.currentUser != null;
|
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'Fitness',
|
title: 'Fitness',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
textTheme:
|
|
||||||
TextTheme(bodyText1: TextStyle(color: ColorConstants.textColor)),
|
|
||||||
fontFamily: 'NotoSansKR',
|
fontFamily: 'NotoSansKR',
|
||||||
scaffoldBackgroundColor: Colors.white,
|
scaffoldBackgroundColor: Colors.white,
|
||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
),
|
),
|
||||||
home: isLoggedIn ? TabBarPage() : OnboardingPage(),
|
//home: isLoggedIn ? TabBarPage() : OnboardingPage(),
|
||||||
|
home: OnboardingPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
37
lib/screens/onboarding/bloc/onboarding_bloc.dart
Normal file
37
lib/screens/onboarding/bloc/onboarding_bloc.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'onboarding_event.dart';
|
||||||
|
import 'onboarding_state.dart';
|
||||||
|
|
||||||
|
class OnboardingBloc extends Bloc<OnboardingEvent, OnboardingState> {
|
||||||
|
OnboardingBloc() : super(OnboardingInitial());
|
||||||
|
|
||||||
|
int pageIndex = 0;
|
||||||
|
|
||||||
|
final pageController = PageController(initialPage: 0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<OnboardingState> mapEventToState(
|
||||||
|
OnboardingEvent event,
|
||||||
|
) async* {
|
||||||
|
if (event is PageChangedEvent) {
|
||||||
|
if (pageIndex == 2) {
|
||||||
|
yield NextScreenState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pageIndex += 1;
|
||||||
|
|
||||||
|
pageController.animateToPage(
|
||||||
|
pageIndex,
|
||||||
|
duration: Duration(milliseconds: 500),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
|
||||||
|
yield PageChangedState(counter: pageIndex);
|
||||||
|
} else if (event is PageSwipedEvent) {
|
||||||
|
pageIndex = event.index;
|
||||||
|
yield PageChangedState(counter: pageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
lib/screens/onboarding/bloc/onboarding_event.dart
Normal file
16
lib/screens/onboarding/bloc/onboarding_event.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//part of 'onboarding_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
abstract class OnboardingEvent {}
|
||||||
|
|
||||||
|
class PageChangedEvent extends OnboardingEvent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageSwipedEvent extends OnboardingEvent {
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
PageSwipedEvent({required this.index});
|
||||||
|
}
|
||||||
18
lib/screens/onboarding/bloc/onboarding_state.dart
Normal file
18
lib/screens/onboarding/bloc/onboarding_state.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//part of 'onboarding_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
abstract class OnboardingState {}
|
||||||
|
|
||||||
|
class OnboardingInitial extends OnboardingState {}
|
||||||
|
|
||||||
|
class PageChangedState extends OnboardingState {
|
||||||
|
final int counter;
|
||||||
|
|
||||||
|
PageChangedState({
|
||||||
|
required this.counter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NextScreenState extends OnboardingState {}
|
||||||
38
lib/screens/onboarding/page/onboarding_page.dart
Normal file
38
lib/screens/onboarding/page/onboarding_page.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../bloc/onboarding_bloc.dart';
|
||||||
|
import '../bloc/onboarding_state.dart';
|
||||||
|
import '../widget/onboarding_content.dart';
|
||||||
|
|
||||||
|
class OnboardingPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: _buildBody(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlocProvider<OnboardingBloc> _buildBody(BuildContext context) {
|
||||||
|
return BlocProvider<OnboardingBloc>(
|
||||||
|
create: (BuildContext context) => OnboardingBloc(),
|
||||||
|
child: BlocConsumer<OnboardingBloc, OnboardingState>(
|
||||||
|
listenWhen: (_, currState) => currState is NextScreenState,
|
||||||
|
listener: (context, state) {
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) {
|
||||||
|
//return SignUpPage();
|
||||||
|
return Text('Test');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
buildWhen: (_, currState) => currState is OnboardingInitial,
|
||||||
|
builder: (context, state) {
|
||||||
|
return OnboardingContent();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
lib/screens/onboarding/widget/onboarding_content.dart
Normal file
114
lib/screens/onboarding/widget/onboarding_content.dart
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import 'package:dots_indicator/dots_indicator.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:percent_indicator/circular_percent_indicator.dart';
|
||||||
|
|
||||||
|
import '../../../core/const/color_constants.dart';
|
||||||
|
import '../../../core/const/data_constants.dart';
|
||||||
|
import '../bloc/onboarding_bloc.dart';
|
||||||
|
import '../bloc/onboarding_event.dart';
|
||||||
|
import '../bloc/onboarding_state.dart';
|
||||||
|
|
||||||
|
class OnboardingContent extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bloc = BlocProvider.of<OnboardingBloc>(context);
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: _createPageView(bloc.pageController, bloc),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _createStatic(bloc),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _createPageView(PageController controller, OnboardingBloc bloc) {
|
||||||
|
return PageView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
controller: controller,
|
||||||
|
children: DataConstants.onboardingTiles,
|
||||||
|
onPageChanged: (index) {
|
||||||
|
bloc.add(PageSwipedEvent(index: index));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _createStatic(OnboardingBloc bloc) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
),
|
||||||
|
BlocBuilder<OnboardingBloc, OnboardingState>(
|
||||||
|
buildWhen: (_, currState) => currState is PageChangedState,
|
||||||
|
builder: (context, state) {
|
||||||
|
return DotsIndicator(
|
||||||
|
dotsCount: 3,
|
||||||
|
position: bloc.pageIndex.toDouble(),
|
||||||
|
decorator: DotsDecorator(
|
||||||
|
color: Colors.grey,
|
||||||
|
activeColor: ColorConstants.primaryColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
BlocBuilder<OnboardingBloc, OnboardingState>(
|
||||||
|
buildWhen: (_, currState) => currState is PageChangedState,
|
||||||
|
builder: (context, state) {
|
||||||
|
final percent = _getPercent(bloc.pageIndex);
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween<double>(begin: 0, end: percent),
|
||||||
|
duration: Duration(seconds: 1),
|
||||||
|
builder: (context, value, _) => CircularPercentIndicator(
|
||||||
|
radius: 110,
|
||||||
|
backgroundColor: ColorConstants.primaryColor,
|
||||||
|
progressColor: Colors.white,
|
||||||
|
percent: 1 - value,
|
||||||
|
center: Material(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
color: ColorConstants.primaryColor,
|
||||||
|
child: RawMaterialButton(
|
||||||
|
shape: CircleBorder(),
|
||||||
|
onPressed: () {
|
||||||
|
bloc.add(PageChangedEvent());
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.east_rounded,
|
||||||
|
size: 38.0,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getPercent(int pageIndex) {
|
||||||
|
switch (pageIndex) {
|
||||||
|
case 0:
|
||||||
|
return 0.25;
|
||||||
|
case 1:
|
||||||
|
return 0.65;
|
||||||
|
case 2:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
lib/screens/onboarding/widget/onboarding_tile.dart
Normal file
46
lib/screens/onboarding/widget/onboarding_tile.dart
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OnboardingTile extends StatelessWidget {
|
||||||
|
final title, imagePath, mainText;
|
||||||
|
|
||||||
|
OnboardingTile({this.imagePath, this.mainText, this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 34),
|
||||||
|
Expanded(
|
||||||
|
child: Image.asset(
|
||||||
|
imagePath,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 65),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: screenWidth / 100,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
mainText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
pubspec.yaml
24
pubspec.yaml
@@ -36,6 +36,8 @@ 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
|
||||||
|
dots_indicator: ^4.0.1
|
||||||
|
percent_indicator: ^4.2.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -53,12 +55,7 @@ dev_dependencies:
|
|||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
# The following section is specific to Flutter packages.
|
||||||
flutter:
|
flutter:
|
||||||
fonts:
|
|
||||||
- family: NotoSansKR
|
|
||||||
fonts:
|
|
||||||
- asset: assets/fonts/NotoSansKR/NotoSansKR-Regular.ttf
|
|
||||||
- asset: assets/fonts/NotoSansKR/NotoSansKR-Bold.ttf
|
|
||||||
weight: 700
|
|
||||||
# The following line ensures that the Material Icons font is
|
# The following line ensures that the Material Icons font is
|
||||||
# included with your application, so that you can use the icons in
|
# included with your application, so that you can use the icons in
|
||||||
# the material Icons class.
|
# the material Icons class.
|
||||||
@@ -66,6 +63,21 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
|
- assets/images/onboarding/
|
||||||
|
- assets/images/auth/
|
||||||
|
- assets/images/home/
|
||||||
|
- assets/images/exercises/
|
||||||
|
- assets/icons/home/
|
||||||
|
- assets/icons/workouts/
|
||||||
|
- assets/icons/social_networks/
|
||||||
|
- assets/videos/workouts/
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
- family: NotoSansKR
|
||||||
|
fonts:
|
||||||
|
- asset: assets/fonts/NotoSansKR/NotoSansKR-Regular.ttf
|
||||||
|
- asset: assets/fonts/NotoSansKR/NotoSansKR-Bold.ttf
|
||||||
|
weight: 700
|
||||||
|
|
||||||
# - images/a_dot_burr.jpeg
|
# - images/a_dot_burr.jpeg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'package:tutorialtest/main.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
await tester.pumpWidget(MyApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
|||||||
Reference in New Issue
Block a user