First draft of Onboarding page

This commit is contained in:
2025-11-13 19:33:58 +01:00
parent 037b8136d9
commit da52453371
22 changed files with 371 additions and 14 deletions

View File

@@ -26,4 +26,4 @@
<orderEntry type="library" name="Flutter for Android" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
</module>

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

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

View 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';
}

View 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.";
}

View File

View File

View File

View File

View File

View File

View 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 {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isLoggedIn = FirebaseAuth.instance.currentUser != null;
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Fitness',
theme: ThemeData(
textTheme:
TextTheme(bodyText1: TextStyle(color: ColorConstants.textColor)),
fontFamily: 'NotoSansKR',
scaffoldBackgroundColor: Colors.white,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: isLoggedIn ? TabBarPage() : OnboardingPage(),
//home: isLoggedIn ? TabBarPage() : OnboardingPage(),
home: OnboardingPage(),
);
}
}

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

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

View 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 {}

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

View 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;
}
}
}

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

View File

@@ -36,6 +36,8 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dots_indicator: ^4.0.1
percent_indicator: ^4.2.5
dev_dependencies:
flutter_test:
@@ -53,12 +55,7 @@ dev_dependencies:
# The following section is specific to Flutter packages.
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
# included with your application, so that you can use the icons in
# the material Icons class.
@@ -66,6 +63,21 @@ flutter:
# To add assets to your application, add an assets section, like this:
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_ham.jpeg

View File

@@ -13,7 +13,7 @@ import 'package:tutorialtest/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);