fix(auth): Fix token refresh

This commit is contained in:
BisratHailu 2026-01-21 09:53:14 +03:00
parent c222b3c67a
commit 1713a8957a
82 changed files with 3357 additions and 1371 deletions

View File

@ -3,7 +3,8 @@
<application <application
android:label="Yimaru" android:label="Yimaru"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

File diff suppressed because one or more lines are too long

View File

@ -26,6 +26,9 @@ import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/api_service.dart'; import 'package:yimaru_app/services/api_service.dart';
import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart';
import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -50,6 +53,8 @@ import 'package:yimaru_app/services/dio_service.dart';
MaterialRoute(page: LearnView), MaterialRoute(page: LearnView),
MaterialRoute(page: LearnLevelView), MaterialRoute(page: LearnLevelView),
MaterialRoute(page: LearnModuleView), MaterialRoute(page: LearnModuleView),
MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [
@ -60,6 +65,7 @@ import 'package:yimaru_app/services/dio_service.dart';
LazySingleton(classType: ApiService), LazySingleton(classType: ApiService),
LazySingleton(classType: SecureStorageService), LazySingleton(classType: SecureStorageService),
LazySingleton(classType: DioService), LazySingleton(classType: DioService),
LazySingleton(classType: StatusCheckerService),
// @stacked-service // @stacked-service
], ],
bottomsheets: [ bottomsheets: [

View File

@ -15,6 +15,7 @@ import '../services/api_service.dart';
import '../services/authentication_service.dart'; import '../services/authentication_service.dart';
import '../services/dio_service.dart'; import '../services/dio_service.dart';
import '../services/secure_storage_service.dart'; import '../services/secure_storage_service.dart';
import '../services/status_checker_service.dart';
final locator = StackedLocator.instance; final locator = StackedLocator.instance;
@ -34,4 +35,5 @@ Future<void> setupLocator({
locator.registerLazySingleton(() => ApiService()); locator.registerLazySingleton(() => ApiService());
locator.registerLazySingleton(() => SecureStorageService()); locator.registerLazySingleton(() => SecureStorageService());
locator.registerLazySingleton(() => DioService()); locator.registerLazySingleton(() => DioService());
locator.registerLazySingleton(() => StatusCheckerService());
} }

View File

@ -5,12 +5,13 @@
// ************************************************************************** // **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:flutter/material.dart' as _i22; import 'package:flutter/material.dart' as _i24;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart' as _i1; import 'package:stacked/stacked.dart' as _i1;
import 'package:stacked_services/stacked_services.dart' as _i23; import 'package:stacked_services/stacked_services.dart' as _i25;
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
as _i10; as _i10;
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23;
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
as _i13; as _i13;
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
@ -37,6 +38,7 @@ import 'package:yimaru_app/ui/views/telegram_support/telegram_support_view.dart'
as _i12; as _i12;
import 'package:yimaru_app/ui/views/terms_and_conditions/terms_and_conditions_view.dart' import 'package:yimaru_app/ui/views/terms_and_conditions/terms_and_conditions_view.dart'
as _i16; as _i16;
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart' as _i22;
class Routes { class Routes {
static const homeView = '/home-view'; static const homeView = '/home-view';
@ -79,6 +81,10 @@ class Routes {
static const learnModuleView = '/learn-module-view'; static const learnModuleView = '/learn-module-view';
static const welcomeView = '/welcome-view';
static const assessmentView = '/assessment-view';
static const all = <String>{ static const all = <String>{
homeView, homeView,
onboardingView, onboardingView,
@ -100,6 +106,8 @@ class Routes {
learnView, learnView,
learnLevelView, learnLevelView,
learnModuleView, learnModuleView,
welcomeView,
assessmentView,
}; };
} }
@ -185,129 +193,154 @@ class StackedRouter extends _i1.RouterBase {
Routes.learnModuleView, Routes.learnModuleView,
page: _i21.LearnModuleView, page: _i21.LearnModuleView,
), ),
_i1.RouteDef(
Routes.welcomeView,
page: _i22.WelcomeView,
),
_i1.RouteDef(
Routes.assessmentView,
page: _i23.AssessmentView,
),
]; ];
final _pagesMap = <Type, _i1.StackedRouteFactory>{ final _pagesMap = <Type, _i1.StackedRouteFactory>{
_i2.HomeView: (data) { _i2.HomeView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i2.HomeView(), builder: (context) => const _i2.HomeView(),
settings: data, settings: data,
); );
}, },
_i3.OnboardingView: (data) { _i3.OnboardingView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i3.OnboardingView(), builder: (context) => const _i3.OnboardingView(),
settings: data, settings: data,
); );
}, },
_i4.StartupView: (data) { _i4.StartupView: (data) {
return _i22.MaterialPageRoute<dynamic>( final args = data.getArgs<StartupViewArguments>(
builder: (context) => const _i4.StartupView(), orElse: () => const StartupViewArguments(),
);
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
settings: data, settings: data,
); );
}, },
_i5.ProfileView: (data) { _i5.ProfileView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i5.ProfileView(), builder: (context) => const _i5.ProfileView(),
settings: data, settings: data,
); );
}, },
_i6.ProfileDetailView: (data) { _i6.ProfileDetailView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i6.ProfileDetailView(), builder: (context) => const _i6.ProfileDetailView(),
settings: data, settings: data,
); );
}, },
_i7.DownloadsView: (data) { _i7.DownloadsView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i7.DownloadsView(), builder: (context) => const _i7.DownloadsView(),
settings: data, settings: data,
); );
}, },
_i8.ProgressView: (data) { _i8.ProgressView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i8.ProgressView(), builder: (context) => const _i8.ProgressView(),
settings: data, settings: data,
); );
}, },
_i9.OngoingProgressView: (data) { _i9.OngoingProgressView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i9.OngoingProgressView(), builder: (context) => const _i9.OngoingProgressView(),
settings: data, settings: data,
); );
}, },
_i10.AccountPrivacyView: (data) { _i10.AccountPrivacyView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i10.AccountPrivacyView(), builder: (context) => const _i10.AccountPrivacyView(),
settings: data, settings: data,
); );
}, },
_i11.SupportView: (data) { _i11.SupportView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i11.SupportView(), builder: (context) => const _i11.SupportView(),
settings: data, settings: data,
); );
}, },
_i12.TelegramSupportView: (data) { _i12.TelegramSupportView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i12.TelegramSupportView(), builder: (context) => const _i12.TelegramSupportView(),
settings: data, settings: data,
); );
}, },
_i13.CallSupportView: (data) { _i13.CallSupportView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i13.CallSupportView(), builder: (context) => const _i13.CallSupportView(),
settings: data, settings: data,
); );
}, },
_i14.LanguageView: (data) { _i14.LanguageView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i14.LanguageView(), builder: (context) => const _i14.LanguageView(),
settings: data, settings: data,
); );
}, },
_i15.PrivacyPolicyView: (data) { _i15.PrivacyPolicyView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i15.PrivacyPolicyView(), builder: (context) => const _i15.PrivacyPolicyView(),
settings: data, settings: data,
); );
}, },
_i16.TermsAndConditionsView: (data) { _i16.TermsAndConditionsView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i16.TermsAndConditionsView(), builder: (context) => const _i16.TermsAndConditionsView(),
settings: data, settings: data,
); );
}, },
_i17.RegisterView: (data) { _i17.RegisterView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i17.RegisterView(), builder: (context) => const _i17.RegisterView(),
settings: data, settings: data,
); );
}, },
_i18.LoginView: (data) { _i18.LoginView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i18.LoginView(), builder: (context) => const _i18.LoginView(),
settings: data, settings: data,
); );
}, },
_i19.LearnView: (data) { _i19.LearnView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i19.LearnView(), builder: (context) => const _i19.LearnView(),
settings: data, settings: data,
); );
}, },
_i20.LearnLevelView: (data) { _i20.LearnLevelView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i20.LearnLevelView(), builder: (context) => const _i20.LearnLevelView(),
settings: data, settings: data,
); );
}, },
_i21.LearnModuleView: (data) { _i21.LearnModuleView: (data) {
return _i22.MaterialPageRoute<dynamic>( return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i21.LearnModuleView(), builder: (context) => const _i21.LearnModuleView(),
settings: data, settings: data,
); );
}, },
_i22.WelcomeView: (data) {
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i22.WelcomeView(),
settings: data,
);
},
_i23.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i24.MaterialPageRoute<dynamic>(
builder: (context) =>
_i23.AssessmentView(key: args.key, data: args.data),
settings: data,
);
},
}; };
@override @override
@ -317,7 +350,61 @@ class StackedRouter extends _i1.RouterBase {
Map<Type, _i1.StackedRouteFactory> get pagesMap => _pagesMap; Map<Type, _i1.StackedRouteFactory> get pagesMap => _pagesMap;
} }
extension NavigatorStateExtension on _i23.NavigationService { class StartupViewArguments {
const StartupViewArguments({
this.key,
this.label = 'Loading',
});
final _i24.Key? key;
final String label;
@override
String toString() {
return '{"key": "$key", "label": "$label"}';
}
@override
bool operator ==(covariant StartupViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.label == label;
}
@override
int get hashCode {
return key.hashCode ^ label.hashCode;
}
}
class AssessmentViewArguments {
const AssessmentViewArguments({
this.key,
required this.data,
});
final _i24.Key? key;
final Map<String, dynamic> data;
@override
String toString() {
return '{"key": "$key", "data": "$data"}';
}
@override
bool operator ==(covariant AssessmentViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.data == data;
}
@override
int get hashCode {
return key.hashCode ^ data.hashCode;
}
}
extension NavigatorStateExtension on _i25.NavigationService {
Future<dynamic> navigateToHomeView([ Future<dynamic> navigateToHomeView([
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -346,14 +433,17 @@ extension NavigatorStateExtension on _i23.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToStartupView([ Future<dynamic> navigateToStartupView({
_i24.Key? key,
String label = 'Loading',
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)? Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition, transition,
]) async { }) async {
return navigateTo<dynamic>(Routes.startupView, return navigateTo<dynamic>(Routes.startupView,
arguments: StartupViewArguments(key: key, label: label),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -598,6 +688,37 @@ extension NavigatorStateExtension on _i23.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToWelcomeView([
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
]) async {
return navigateTo<dynamic>(Routes.welcomeView,
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToAssessmentView({
_i24.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView([ Future<dynamic> replaceWithHomeView([
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -626,14 +747,17 @@ extension NavigatorStateExtension on _i23.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithStartupView([ Future<dynamic> replaceWithStartupView({
_i24.Key? key,
String label = 'Loading',
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)? Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition, transition,
]) async { }) async {
return replaceWith<dynamic>(Routes.startupView, return replaceWith<dynamic>(Routes.startupView,
arguments: StartupViewArguments(key: key, label: label),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -877,4 +1001,35 @@ extension NavigatorStateExtension on _i23.NavigationService {
parameters: parameters, parameters: parameters,
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithWelcomeView([
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
]) async {
return replaceWith<dynamic>(Routes.welcomeView,
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithAssessmentView({
_i24.Key? key,
required Map<String, dynamic> data,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.assessmentView,
arguments: AssessmentViewArguments(key: key, data: data),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
} }

View File

@ -27,8 +27,8 @@ class MainApp extends StatelessWidget {
Widget _buildMaterialApp() => MaterialApp( Widget _buildMaterialApp() => MaterialApp(
initialRoute: Routes.startupView, initialRoute: Routes.startupView,
theme: ThemeData(fontFamily: 'Aeonik'), theme: ThemeData(fontFamily: 'Aeonik'),
onGenerateRoute: StackedRouter().onGenerateRoute,
navigatorKey: StackedService.navigatorKey, navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
navigatorObservers: [StackedService.routeObserver], navigatorObservers: [StackedService.routeObserver],
); );
} }

View File

@ -0,0 +1,20 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/option.dart';
import 'package:yimaru_app/models/question.dart';
part 'assessment.g.dart';
@JsonSerializable()
class Assessment {
@JsonKey(name: 'Question')
final Question? question;
@JsonKey(name: 'Options')
final List<Option>? options;
const Assessment({this.options, this.question});
factory Assessment.fromJson(Map<String, dynamic> json) =>
_$AssessmentFromJson(json);
Map<String, dynamic> toJson() => _$AssessmentToJson(this);
}

View File

@ -0,0 +1,22 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'assessment.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Assessment _$AssessmentFromJson(Map<String, dynamic> json) => Assessment(
options: (json['Options'] as List<dynamic>?)
?.map((e) => Option.fromJson(e as Map<String, dynamic>))
.toList(),
question: json['Question'] == null
? null
: Question.fromJson(json['Question'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AssessmentToJson(Assessment instance) =>
<String, dynamic>{
'Question': instance.question,
'Options': instance.options,
};

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'option.g.dart';
@JsonSerializable()
class Option {
@JsonKey(name: 'question_id')
final int? questionId;
@JsonKey(name: 'option_text')
final String? optionText;
const Option({this.optionText, this.questionId});
factory Option.fromJson(Map<String, dynamic> json) => _$OptionFromJson(json);
Map<String, dynamic> toJson() => _$OptionToJson(this);
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'option.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Option _$OptionFromJson(Map<String, dynamic> json) => Option(
optionText: json['option_text'] as String?,
questionId: (json['question_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$OptionToJson(Option instance) => <String, dynamic>{
'question_id': instance.questionId,
'option_text': instance.optionText,
};

View File

@ -0,0 +1,36 @@
import 'package:json_annotation/json_annotation.dart';
part 'question.g.dart';
@JsonSerializable()
class Question {
final int? id;
final int? points;
final String? title;
final String? description;
@JsonKey(name: 'is_active')
final bool? isActive;
@JsonKey(name: 'question_type')
final String? questionType;
@JsonKey(name: 'difficulty_level')
final String? difficultyLevel;
const Question(
{this.id,
this.title,
this.points,
this.isActive,
this.description,
this.questionType,
this.difficultyLevel});
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
Map<String, dynamic> toJson() => _$QuestionToJson(this);
}

View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'question.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Question _$QuestionFromJson(Map<String, dynamic> json) => Question(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
points: (json['points'] as num?)?.toInt(),
isActive: json['is_active'] as bool?,
description: json['description'] as String?,
questionType: json['question_type'] as String?,
difficultyLevel: json['difficulty_level'] as String?,
);
Map<String, dynamic> _$QuestionToJson(Question instance) => <String, dynamic>{
'id': instance.id,
'points': instance.points,
'title': instance.title,
'description': instance.description,
'is_active': instance.isActive,
'question_type': instance.questionType,
'difficulty_level': instance.difficultyLevel,
};

View File

@ -7,13 +7,19 @@ class UserModel {
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id')
final int? userId; final int? userId;
final bool? profileCompleted;
@JsonKey(name: 'access_token') @JsonKey(name: 'access_token')
final String? accessToken; final String? accessToken;
@JsonKey(name: 'refresh_token') @JsonKey(name: 'refresh_token')
final String? refreshToken; final String? refreshToken;
UserModel({this.userId, this.accessToken, this.refreshToken}); const UserModel(
{this.userId,
this.accessToken,
this.profileCompleted,
this.refreshToken});
factory UserModel.fromJson(Map<String, dynamic> json) => factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json); _$UserModelFromJson(json);

View File

@ -9,11 +9,13 @@ part of 'user_model.dart';
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel( UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
userId: (json['user_id'] as num?)?.toInt(), userId: (json['user_id'] as num?)?.toInt(),
accessToken: json['access_token'] as String?, accessToken: json['access_token'] as String?,
profileCompleted: json['profileCompleted'] as bool?,
refreshToken: json['refresh_token'] as String?, refreshToken: json['refresh_token'] as String?,
); );
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
'user_id': instance.userId, 'user_id': instance.userId,
'profileCompleted': instance.profileCompleted,
'access_token': instance.accessToken, 'access_token': instance.accessToken,
'refresh_token': instance.refreshToken, 'refresh_token': instance.refreshToken,
}; };

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:yimaru_app/models/assessment.dart';
import 'package:yimaru_app/models/user_model.dart'; import 'package:yimaru_app/models/user_model.dart';
import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/common/app_constants.dart';
@ -9,32 +10,12 @@ import '../ui/common/enmus.dart';
class ApiService { class ApiService {
final _service = locator<DioService>(); final _service = locator<DioService>();
// Http headers
Map<String, dynamic> _getHeaders({String? token}) => {
// if (token != null) 'Authorization': 'Bearer $token',
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8',
if (token != null) 'Authorization': 'Bearer $token'
};
// Dio options
Options? _getOptions({String? token}) {
return Options(
// followRedirects: false,
// validateStatus: (status) => true,
headers: _getHeaders(token: token),
);
}
// Register // Register
Future<Map<String, dynamic>> register(Map<String, dynamic> data) async { Future<Map<String, dynamic>> register(Map<String, dynamic> data) async {
try { try {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kRegisterUrl', '$baseUrl/$userUrl/$kRegisterUrl',
data: data, data: data,
options: _getOptions(),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -62,7 +43,6 @@ class ApiService {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$kLoginUrl', '$baseUrl/$kLoginUrl',
data: data, data: data,
options: _getOptions(),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -91,7 +71,6 @@ class ApiService {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kVerifyOtpUrl', '$baseUrl/$userUrl/$kVerifyOtpUrl',
data: data, data: data,
options: _getOptions(),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
return { return {
@ -119,7 +98,6 @@ class ApiService {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kResendOtpUrl', '$baseUrl/$userUrl/$kResendOtpUrl',
data: data, data: data,
options: _getOptions(),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -146,7 +124,6 @@ class ApiService {
try { try {
Response response = await _service.dio.get( Response response = await _service.dio.get(
'$baseUrl/$userUrl/${user?.userId}/$kProfileStatusUrl', '$baseUrl/$userUrl/${user?.userId}/$kProfileStatusUrl',
options: _getOptions(token: user?.accessToken),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -168,4 +145,60 @@ class ApiService {
}; };
} }
} }
// Update profile
Future<Map<String, dynamic>> updateProfile(
{required UserModel? user, required Map<String, dynamic> data}) async {
try {
Response response = await _service.dio.put(
'$baseUrl/$userUrl',
data: data,
);
print(response.statusCode);
print(response.data);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'message': 'Profile updated successfully'
};
} else {
return {
'status': ResponseStatus.failure,
'message': 'Unknown Error Occurred'
};
}
} catch (e) {
print('Exception ${e.toString()}');
return {
'message': e.toString(),
'status': ResponseStatus.failure,
};
}
}
// Assessments
Future<List<Assessment>> getAssessments() async {
try {
List<Assessment> assessments = [];
final Response response =
await _service.dio.get('$baseUrl/$kAssessmentsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
assessments = decodedData.map(
(e) {
return Assessment.fromJson(e);
},
).toList();
return assessments;
}
return [];
} catch (e) {
return [];
}
}
} }

View File

@ -12,21 +12,52 @@ class AuthenticationService {
return false; return false;
} }
Future<String?> getAccessToken() async =>
await _secureService.getString('accessToken');
Future<String?> getRefreshToken() async =>
await _secureService.getString('refreshToken');
Future<int?> getUserId() async => await _secureService.getInt('userId');
Future<void> saveTokens({
required String access,
required String refresh,
}) async {
await _secureService.setString('accessToken', access);
await _secureService.setString('refreshToken', refresh);
}
Future<void> saveUserData(Map<String, dynamic> data) async { Future<void> saveUserData(Map<String, dynamic> data) async {
await _secureService.setInt('userId', data['userId']); await _secureService.setInt('userId', data['userId']);
await _secureService.setString('accessToken', data['accessToken']); await _secureService.setString('accessToken', data['accessToken']);
await _secureService.setString('refreshToken', data['refreshToken']); await _secureService.setString('refreshToken', data['refreshToken']);
} }
Future<void> saveProfileCompleted(bool value) async {
await _secureService.setBool('profileCompleted', value);
}
Future<bool> isFirstTimeInstall() async =>
await _secureService.getBool('firstTimeInstall') ?? true;
Future<void> setFirstTimeInstall(bool value) async {
await _secureService.setBool('firstTimeInstall', value);
}
Future<UserModel> getUser() async { Future<UserModel> getUser() async {
UserModel user = UserModel( UserModel user = UserModel(
userId: await _secureService.getInt('userId'), userId: await _secureService.getInt('userId'),
accessToken: await _secureService.getString('accessToken'), accessToken: await _secureService.getString('accessToken'),
refreshToken: await _secureService.getString('refreshToken')); refreshToken: await _secureService.getString('refreshToken'),
profileCompleted: await _secureService.getBool('profileCompleted'),
);
return user; return user;
} }
Future<void> logOut() async { Future<void> logOut() async {
bool firstTimeInstall = await isFirstTimeInstall();
await _secureService.clear(); await _secureService.clear();
await setFirstTimeInstall(firstTimeInstall);
} }
} }

View File

@ -1,41 +1,175 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/user_model.dart';
import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/secure_storage_service.dart';
import '../app/app.locator.dart';
import '../ui/common/app_constants.dart'; import '../ui/common/app_constants.dart';
class DioService { class DioService {
final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
final Dio _dio = Dio(); final Dio _dio = Dio();
final Dio _refreshDio = Dio(); // separate instance
bool _isRefreshing = false;
final List<void Function()> _retryQueue = [];
DioService() { DioService() {
_dio.options.baseUrl = baseUrl; _dio.options
_dio.options.connectTimeout = const Duration(seconds: 30); ..baseUrl = baseUrl
_dio.options.receiveTimeout = const Duration(seconds: 30); ..connectTimeout = const Duration(seconds: 30)
..receiveTimeout = const Duration(seconds: 30);
_dio.interceptors.add( _dio.interceptors.add(
InterceptorsWrapper( InterceptorsWrapper(
onRequest: (options, handler) { onError: _onError,
debugPrint('➡️➡️➡️ REQUEST ➡️➡️➡️'); onRequest: _onRequest,
debugPrint('➡️ Data: ${options.data}'); onResponse: _onResponse,
debugPrint('➡️ Headers: ${options.headers}');
debugPrint('➡️ ${options.method} ${options.uri}');
handler.next(options);
},
onResponse: (response, handler) {
debugPrint('✅✅✅ RESPONSE ✅✅✅');
debugPrint('✅ Data : ${response.data}');
debugPrint('✅ Status Code : ${response.statusCode}');
handler.next(response);
},
onError: (error, handler) {
debugPrint('❌❌❌ ERROR ❌❌❌');
debugPrint('${error.message}');
debugPrint('❌ URI: ${error.requestOptions.uri}');
debugPrint('❌ Headers sent: ${error.requestOptions.headers}');
handler.next(error);
},
), ),
); );
} }
void _onResponse(
Response response,
ResponseInterceptorHandler handler,
) {
debugPrint('✅✅✅✅INITIALIZING RESPONSE✅✅✅✅');
debugPrint('${response.statusCode} ${response.requestOptions.uri}');
debugPrint('✅ DATA: ${response.data}');
debugPrint('✅✅✅✅FINALIZING RESPONSE✅✅✅✅');
handler.next(response);
}
Future<void> _onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
final token = await _authenticationService.getAccessToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
options.headers['Accept'] = 'application/json';
options.headers['Content-Type'] = 'application/json';
debugPrint('INITIALIZING REQUEST➡');
debugPrint('➡️ ${options.method} ${options.uri}');
debugPrint('➡️ HEADERS: ${options.headers}');
debugPrint('➡️ DATA: ${options.data}');
debugPrint('FINALIZING REQUEST➡');
handler.next(options);
}
Future<void> _onError(
DioException error,
ErrorInterceptorHandler handler,
) async {
debugPrint('❌❌❌❌INITIALIZING ERROR❌❌❌❌');
debugPrint('${error.response?.data} ${error.requestOptions.uri}');
debugPrint('${error.response?.statusCode} ${error.requestOptions.uri}');
debugPrint('❌❌❌❌FINALIZING ERROR❌❌❌❌');
if (error.response?.statusCode == 401 &&
!_isRefreshRequest(error.requestOptions)) {
return _handle401(error, handler);
}
handler.next(error);
}
Future<void> _handle401(
DioException error,
ErrorInterceptorHandler handler,
) async {
final requestOptions = error.requestOptions;
if (_isRefreshing) {
_retryQueue.add(() async {
final response = await _dio.fetch(requestOptions);
handler.resolve(response);
});
return;
}
_isRefreshing = true;
try {
final refreshed = await _refreshToken();
if (!refreshed) {
handler.reject(error);
return;
}
final response = await _dio.fetch(requestOptions);
for (final retry in _retryQueue) {
retry();
}
_retryQueue.clear();
handler.resolve(response);
} catch (e) {
handler.reject(error);
} finally {
_isRefreshing = false;
}
}
Future<bool> _refreshToken() async {
final UserModel user = await _authenticationService.getUser();
if (user.refreshToken == null) return false;
try {
Map<String,dynamic> data = {
'role': 'STUDENT',
'user_id': user.userId,
'access_token': user.accessToken,
'refresh_token': user.refreshToken
};
print(data);
final response = await _refreshDio.post(
'$baseUrl/$kRefreshTokenUrl',
data: data,
options: Options(
followRedirects: false,
validateStatus: (status) => true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
);
print('Refresh response');
print(response.data);
await _authenticationService.saveTokens(
access: response.data['access_token'],
refresh: response.data['refresh_token'],
);
return true;
} catch (e) {
print('Refresh response exception');
print(e.toString());
// await _authenticationService.logOut();
// await _navigationService.replaceWithLoginView();
return false;
}
}
bool _isRefreshRequest(RequestOptions options) {
return options.path.contains(kRefreshTokenUrl);
}
Dio get dio => _dio; Dio get dio => _dio;
} }

View File

@ -0,0 +1,88 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart';
import 'package:in_app_update/in_app_update.dart';
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:storage_info/storage_info.dart';
import 'package:yimaru_app/services/secure_storage_service.dart';
import '../app/app.locator.dart';
class StatusCheckerService {
final storage = locator<SecureStorageService>();
bool _previousConnection = true;
bool get previousConnection => _previousConnection;
Future<int> getBatteryLevel() async {
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
Future<bool> userAuthenticated() async {
await checkAndUpdate();
if (await storage.getString('authenticated') != null) {
return true;
}
return false;
}
Future<bool> checkConnection() async {
if (await InternetConnection().hasInternetAccess) {
_previousConnection = true;
return true;
} else {
if (_previousConnection) {
// showErrorToast('Check your internet connection');
_previousConnection = false;
}
return false;
}
}
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
Future<void> checkAndUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// KewedeConst().showErrorToast(
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// KewedeConst()
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// KewedeConst()
// .showErrorToast('Unable to update app, please free up space.');
}
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
return; // Prevent update from starting
}
try {
if (await checkConnection()) {
await InAppUpdate
.checkForUpdate(); // Continue update only if sufficient resources available
}
// ... rest of your update logic ...
} on PlatformException {
// Handle specific error code for better user experience and potentially different error messages for each issue
}
}
}

View File

@ -1,5 +1,5 @@
//String baseUrl = 'http://195.35.29.82:8080'; String baseUrl = 'http://195.35.29.82:8080';
String baseUrl = 'https://api.yimaru.yaltopia.com'; //String baseUrl = 'https://api.yimaru.yaltopia.com';
String userUrl = 'api/v1/user'; String userUrl = 'api/v1/user';
@ -9,6 +9,10 @@ String kVerifyOtpUrl = 'verify-otp';
String kResendOtpUrl = 'resend-otp'; String kResendOtpUrl = 'resend-otp';
String kRefreshTokenUrl = 'api/v1/auth/refresh';
String kLoginUrl = 'api/v1/auth/customer-login'; String kLoginUrl = 'api/v1/auth/customer-login';
String kProfileStatusUrl = 'is-profile-completed'; String kProfileStatusUrl = 'is-profile-completed';
String kAssessmentsUrl = 'api/v1/assessment/questions';

View File

@ -5,3 +5,6 @@ enum RegistrationType { phone, email }
enum ResponseStatus { success, failure } enum ResponseStatus { success, failure }
enum LearnLevelStatus { pending, started, completed } enum LearnLevelStatus { pending, started, completed }
// Levels
enum ProficiencyLevels { a1, a2, b1, b2, none }

View File

@ -0,0 +1,17 @@
Map<String, String> splitFullName(String fullName) {
final parts = fullName.trim().split(RegExp(r'\s+'));
if (parts.length == 1) {
return {
'last_name': '',
'nick_name': parts[0],
'first_name': parts[0],
};
}
return {
'nick_name': parts.first,
'first_name': parts.first,
'last_name': parts.sublist(1).join(' '),
};
}

View File

@ -252,7 +252,8 @@ void showErrorToast(String message) {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
style: ToastificationStyle.fillColored, style: ToastificationStyle.fillColored,
description: buildToastDescription(message), description: buildToastDescription(message),
autoCloseDuration: const Duration(seconds: 10), borderSide: const BorderSide(color: kcWhite),
autoCloseDuration: const Duration(seconds: 5),
margin: const EdgeInsets.symmetric(horizontal: 15), margin: const EdgeInsets.symmetric(horizontal: 15),
); );
} }
@ -269,7 +270,8 @@ void showSuccessToast(String message) {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
style: ToastificationStyle.fillColored, style: ToastificationStyle.fillColored,
description: buildToastDescription(message), description: buildToastDescription(message),
autoCloseDuration: const Duration(seconds: 10), borderSide: const BorderSide(color: kcWhite),
autoCloseDuration: const Duration(seconds: 5),
margin: const EdgeInsets.symmetric(horizontal: 15), margin: const EdgeInsets.symmetric(horizontal: 15),
); );
} }

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/assessment/screens/Assessment_form_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_completion_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_failure_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/result_analysis_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/retake_assessment_screen.dart';
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
import 'assessment_viewmodel.dart';
class AssessmentView extends StackedView<AssessmentViewModel> {
final Map<String, dynamic> data;
const AssessmentView({Key? key, required this.data}) : super(key: key);
@override
void onViewModelReady(AssessmentViewModel viewModel) {
viewModel.getAssessments();
viewModel.initUserData(data);
super.onViewModelReady(viewModel);
}
@override
Widget builder(
BuildContext context,
AssessmentViewModel viewModel,
Widget? child,
) =>
_buildAssessmentScreens(viewModel);
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack(
index: viewModel.currentPage,
children: _buildScreens(),
);
List<Widget> _buildScreens() => [
_buildAssessmentIntro(),
_buildAssessment(),
// _buildAssessmentFailure(),
// _buildRetakeAssessment(),
// _buildResultAnalysis(),
// _buildAssessmentCompletion(),
_buildAssessmentResult(),
_buildStartLesson(),
];
Widget _buildAssessmentIntro() => const AssessmentIntroScreen();
Widget _buildAssessment() => const AssessmentFormScreen();
Widget _buildAssessmentFailure() => const AssessmentFailureScreen();
Widget _buildRetakeAssessment() => const RetakeAssessmentScreen();
Widget _buildResultAnalysis() => const ResultAnalysisScreen();
Widget _buildAssessmentCompletion() => const AssessmentCompletionScreen();
Widget _buildAssessmentResult() => const AssessmentResultScreen();
Widget _buildStartLesson() => const StartLessonScreen();
@override
AssessmentViewModel viewModelBuilder(
BuildContext context,
) =>
AssessmentViewModel();
}

View File

@ -0,0 +1,252 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/assessment.dart';
import '../../../models/user_model.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../home/home_view.dart';
class AssessmentViewModel extends BaseViewModel {
final _apiService = locator<ApiService>();
final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
int _currentPage = 0;
int get currentPage => _currentPage;
final PageController _pageController = PageController();
PageController get pageController => _pageController;
int _previousPage = 0;
int get previousPage => _previousPage;
// Assessment
int _currentQuestion = 0;
int get currentQuestion => _currentQuestion;
ProficiencyLevels _proficiencyLevel = ProficiencyLevels.none;
ProficiencyLevels get proficiencyLevel => _proficiencyLevel;
List<Assessment> _assessments = [];
List<Assessment> get assessments => _assessments;
final Map<String, dynamic> _selectedAnswers = {};
Map<String, dynamic> get selectedAnswers => _selectedAnswers;
// User data
final Map<String, dynamic> _userData = {};
Map<String, dynamic> get userData => _userData;
// Assessment
int countCorrectAnswersUntil(int untilQuestion) {
int count = 0;
for (int i = 1; i <= untilQuestion; i++) {
final answer = _selectedAnswers[i.toString()];
if (answer is Map<String, dynamic> && answer['correct'] == true) {
count++;
}
}
return count;
}
Map<String, dynamic> evaluateAssessment() {
if (_currentQuestion == 5) {
// A1
final correctCount = countCorrectAnswersUntil(5);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct A1: $correctCount');
if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a1};
} else {
return {'continue': false, 'level': ProficiencyLevels.a1};
}
} else if (_currentQuestion == 10) {
// A2
final correctCount = countCorrectAnswersUntil(10);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct A2: $correctCount');
if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a2};
} else {
return {'continue': false, 'level': ProficiencyLevels.a2};
}
} else if (_currentQuestion == 16) {
// B1
final correctCount = countCorrectAnswersUntil(16);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct B1: $correctCount');
if (correctCount > 4) {
return {'continue': true, 'level': ProficiencyLevels.b1};
} else {
return {'continue': false, 'level': ProficiencyLevels.b1};
}
} else if (_currentQuestion == 22) {
final correctCount = countCorrectAnswersUntil(16);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct B2: $correctCount');
if (correctCount > 4) {
return {'continue': true, 'level': ProficiencyLevels.b2};
} else {
return {'continue': false, 'level': ProficiencyLevels.b2};
}
} else {
return {'continue': true, 'level': ProficiencyLevels.none};
}
}
void setSelectedAnswer({required int question, required String option}) {
bool correct = false;
final generator = Random();
int random = generator.nextInt(4);
if (option == _assessments[question - 1].options?[random].optionText) {
correct = true;
}
final data = {
question.toString(): {
'option': option,
'correct': correct,
'answer': _assessments[question - 1].options?[random].optionText
}
};
_selectedAnswers.addAll(data);
rebuildUi();
}
bool isSelectedAnswer({required int question, required String answer}) {
return _selectedAnswers[question.toString()]?['option'] == answer;
}
Future<void> getAssessments() async {
_assessments = await runBusyFuture<List<Assessment>>(_getAssessments());
}
Future<List<Assessment>> _getAssessments() async {
List<Assessment> response = await _apiService.getAssessments();
for (int i = 0; i < 6; i++) {
final generator = Random();
int random = generator.nextInt(15);
response.add(response[random]);
}
return response;
}
// Add user data
void initUserData(Map<String, dynamic> data) {
clearUserData();
_userData.addAll(data);
}
void addUserData(Map<String, dynamic> data) {
_userData.addAll(data);
}
void clearUserData() {
_userData.clear();
}
// Complete profile
Future<void> completeProfile() async {
Map<String, dynamic> response =
await runBusyFuture<Map<String, dynamic>>(_completeProfile());
}
Future<Map<String, dynamic>> _completeProfile() async {
print(_userData);
UserModel user = await _authenticationService.getUser();
Map<String, dynamic> response =
await _apiService.updateProfile(data: _userData, user: user);
return response;
}
// Navigation
void nextQuestion() {
_currentQuestion++;
Map<String, dynamic> response = evaluateAssessment();
if (response['level'] == ProficiencyLevels.none) {
_pageController.jumpToPage(_currentQuestion);
} else {
if (response['continue']) {
_pageController.jumpToPage(_currentQuestion);
}
{
_proficiencyLevel = response['level'];
next();
}
}
rebuildUi();
}
void previousQuestion() {
if (_currentQuestion != 0) {
_currentQuestion--;
_pageController.previousPage(
duration: const Duration(microseconds: 100), curve: Curves.linear);
rebuildUi();
} else {
_navigationService.back();
}
}
void next({int? page}) async {
if (page == null) {
if (_previousPage != 0) {
_currentPage = _previousPage;
} else {
_currentPage++;
}
} else {
_previousPage = _currentPage;
_currentPage = page;
}
rebuildUi();
}
void pop() {
if (_currentPage != 0) {
_currentPage--;
rebuildUi();
}
}
Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView();
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView());
}

View File

@ -3,28 +3,28 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AssessmentCompletionScreen extends ViewModelWidget<OnboardingViewModel> { class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentCompletionScreen({super.key}); const AssessmentCompletionScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)]; [_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildAppBar() => const LargeAppBar( Widget _buildAppBar() => const LargeAppBar(
@ -32,30 +32,30 @@ class AssessmentCompletionScreen extends ViewModelWidget<OnboardingViewModel> {
showLanguageSelection: false, showLanguageSelection: false,
); );
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildIcon(), _buildIcon(),
verticalSpaceMedium, verticalSpaceMedium,
@ -84,12 +84,12 @@ class AssessmentCompletionScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,

View File

@ -4,62 +4,61 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> { import '../assessment_viewmodel.dart';
class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentFailureScreen({super.key}); const AssessmentFailureScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildIcon(), _buildIcon(),
verticalSpaceMedium, verticalSpaceMedium,
@ -86,18 +85,18 @@ class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel), children: _buildLowerColumnChildren(viewModel),
); );
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel) _buildSkipButtonWrapper(viewModel)
]; ];
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
@ -108,12 +107,12 @@ class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel), child: _buildSkipButton(viewModel),
); );
Widget _buildSkipButton(OnboardingViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Skip', text: 'Skip',

View File

@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../assessment_viewmodel.dart';
import 'assessment_loading_screen.dart';
class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentFormScreen({super.key});
//final PageController _pageController = PageController();
@override
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildAssessmentScreens(viewModel);
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) =>
viewModel.isBusy
? _buildPageLoadingIndicator()
: _buildAssessmentScreensWrapper(viewModel);
Widget _buildPageLoadingIndicator() => const AssessmentLoadingScreen();
Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) =>
PopScope(
canPop: false,
onPopInvokedWithResult: (value, data) => viewModel.previousQuestion(),
child: _buildScaffoldWrapper(viewModel));
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: true,
showLanguageSelection: false,
onPop: viewModel.previousQuestion,
);
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessment(viewModel),
);
Widget _buildAssessment(AssessmentViewModel viewModel) => PageView.builder(
controller: viewModel.pageController,
itemCount: viewModel.assessments.length,
itemBuilder: (cotext, index) =>
_buildBody(index: index, viewModel: viewModel));
Widget _buildBody(
{required int index, required AssessmentViewModel viewModel}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel: viewModel, index: index),
);
List<Widget> _buildBodyChildren(
{required int index, required AssessmentViewModel viewModel}) =>
[
_buildUpperColumnWrapper(viewModel: viewModel, index: index),
_buildContinueButtonWrapper(viewModel: viewModel, question: index + 1)
];
Widget _buildUpperColumnWrapper(
{required int index, required AssessmentViewModel viewModel}) =>
Expanded(
child: _buildUpperColumn(index: index, viewModel: viewModel),
);
Widget _buildUpperColumn(
{required int index, required AssessmentViewModel viewModel}) =>
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(index: index, viewModel: viewModel),
);
List<Widget> _buildUpperColumnChildren(
{required int index, required AssessmentViewModel viewModel}) =>
[
verticalSpaceMedium,
_buildTitle(index: index, viewModel: viewModel),
verticalSpaceMedium,
_buildAnswers(index: index, viewModel: viewModel)
];
Widget _buildTitle(
{required int index, required AssessmentViewModel viewModel}) =>
Text(
'Q${index + 1}. ${viewModel.assessments[index].question?.title} ',
style: style16DG600,
);
Widget _buildAnswers(
{required int index, required AssessmentViewModel viewModel}) =>
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.assessments[index].options?.length,
itemBuilder: (context, inner) => _buildAnswer(
title: viewModel.assessments[index].options?[inner].optionText ?? '',
selected: viewModel.isSelectedAnswer(
question: index + 1,
answer: viewModel.assessments[index].options?[inner].optionText ??
''),
onTap: () => viewModel.setSelectedAnswer(
question: index + 1,
option: viewModel.assessments[index].options?[inner].optionText ??
''),
),
);
Widget _buildAnswer(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
CustomSmallRadioButton(
title: title,
onTap: onTap,
selected: selected,
);
Widget _buildContinueButtonWrapper(
{required int question, required AssessmentViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel: viewModel, question: question),
);
Widget _buildContinueButton(
{required int question, required AssessmentViewModel viewModel}) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
text: viewModel.currentQuestion == viewModel.assessments.length - 1
? 'Finish'
: 'Continue',
backgroundColor:
viewModel.selectedAnswers.containsKey(question.toString())
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.selectedAnswers.containsKey(question.toString())
?
// viewModel.currentQuestion == viewModel.assessments.length - 1
// ? () => viewModel.next()
// :
() => viewModel.nextQuestion()
: null,
);
}

View File

@ -3,66 +3,65 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> { import '../assessment_viewmodel.dart';
class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentIntroScreen({super.key}); const AssessmentIntroScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildSubTitle(), _buildSubTitle(),
]; ];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Want a quick assessment to know your English level?', 'Want a quick assessment to know your English level?',
@ -78,18 +77,18 @@ class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel), children: _buildLowerColumnChildren(viewModel),
); );
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel) _buildSkipButtonWrapper(viewModel)
]; ];
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
@ -100,18 +99,19 @@ class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel), child: _buildSkipButton(viewModel),
); );
Widget _buildSkipButton(OnboardingViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
const CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Skip', text: 'Skip',
borderRadius: 12, borderRadius: 12,
borderColor: kcPrimaryColor,
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => viewModel.next(page: 3),
); );
} }

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../../common/app_colors.dart';
import '../../../widgets/large_app_bar.dart';
class AssessmentLoadingScreen extends StatelessWidget {
const AssessmentLoadingScreen({super.key});
@override
Widget build(BuildContext context) => _buildScaffoldWrapper();
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(),
);
Widget _buildScaffold() => Stack(
children: [_buildColumn(), _buildPageIndicator()],
);
Widget _buildColumn() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [_buildAppBar(), _buildBody()];
Widget _buildAppBar() => const LargeAppBar(
showBackButton: true,
showLanguageSelection: true,
);
Widget _buildBody() => Expanded(child: Container());
Widget _buildPageIndicator() => const PageLoadingIndicator();
}

View File

@ -4,64 +4,63 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> { import '../assessment_viewmodel.dart';
class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentResultScreen({super.key}); const AssessmentResultScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildTitle(), _buildTitle(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildPrimarySubTitle(), _buildPrimarySubTitle(),
verticalSpaceMedium, verticalSpaceMedium,
@ -70,10 +69,10 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSecondarySubTitle() _buildSecondarySubTitle()
]; ];
Widget _buildTitle() => const Text( Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a B1 speaker!', 'Youre likely a ${viewModel.proficiencyLevel.name.toUpperCase()} speaker!',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: const TextStyle(
fontSize: 25, fontSize: 25,
color: kcPrimaryColor, color: kcPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -94,18 +93,18 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel), children: _buildLowerColumnChildren(viewModel),
); );
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel) _buildSkipButtonWrapper(viewModel)
]; ];
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
@ -116,12 +115,12 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel), child: _buildSkipButton(viewModel),
); );
Widget _buildSkipButton(OnboardingViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,

View File

@ -3,45 +3,46 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class ResultAnalysisScreen extends ViewModelWidget<OnboardingViewModel> { import '../assessment_viewmodel.dart';
class ResultAnalysisScreen extends ViewModelWidget<AssessmentViewModel> {
const ResultAnalysisScreen({super.key}); const ResultAnalysisScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceMassive, verticalSpaceMassive,
_buildIcon(), _buildIcon(),
verticalSpaceMedium, verticalSpaceMedium,
@ -50,11 +51,11 @@ class ResultAnalysisScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(), _buildSubTitle(),
]; ];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop(language: false)); );
Widget _buildIcon() => SvgPicture.asset( Widget _buildIcon() => SvgPicture.asset(
'assets/icons/progress_indicator.svg', 'assets/icons/progress_indicator.svg',

View File

@ -3,54 +3,55 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> { import '../assessment_viewmodel.dart';
class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
const RetakeAssessmentScreen({super.key}); const RetakeAssessmentScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)]; [_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildIcon(), _buildIcon(),
verticalSpaceMedium, verticalSpaceMedium,
@ -59,11 +60,11 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(), _buildSubTitle(),
]; ];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop(language: false)); );
Widget _buildIcon() => const Icon( Widget _buildIcon() => const Icon(
Icons.warning_amber_rounded, Icons.warning_amber_rounded,
@ -87,18 +88,18 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column( Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel), children: _buildLowerColumnChildren(viewModel),
); );
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel) _buildSkipButtonWrapper(viewModel)
]; ];
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
@ -109,12 +110,12 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel), child: _buildSkipButton(viewModel),
); );
Widget _buildSkipButton(OnboardingViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Skip', text: 'Skip',

View File

@ -4,27 +4,42 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> { import '../../../common/enmus.dart';
import '../assessment_viewmodel.dart';
class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
const StartLessonScreen({super.key}); const StartLessonScreen({super.key});
Future<void> _start(AssessmentViewModel viewModel) async {
if (viewModel.proficiencyLevel != ProficiencyLevels.none) {
Map<String, dynamic> data = {
'preferred_language': 'en',
'knowledge_level': viewModel.proficiencyLevel.name.toUpperCase()
};
viewModel.addUserData(data);
}
await viewModel.completeProfile();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)]; [_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildAppBar() => const LargeAppBar( Widget _buildAppBar() => const LargeAppBar(
@ -32,52 +47,52 @@ class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> {
showLanguageSelection: false, showLanguageSelection: false,
); );
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(OnboardingViewModel viewModel) => Column( Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) => List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)]; [_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column( Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildIcon(), _buildIcon(),
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSubTitle(), _buildSubTitle(),
]; ];
Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg'); Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg');
Widget _buildTitle() => const Text.rich( Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
TextSpan( TextSpan(
text: 'Welcome aboard', text: 'Welcome aboard',
style: TextStyle( style: const TextStyle(
fontSize: 25, fontSize: 25,
color: kcDarkGrey, color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
children: [ children: [
TextSpan( TextSpan(
text: ', Bisrat!', text: ', ${viewModel.userData['first_name']}!',
style: TextStyle( style: const TextStyle(
fontSize: 25, fontSize: 25,
color: kcPrimaryColor, color: kcPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -91,18 +106,18 @@ class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Go to My Lessons', text: 'Go to My Lessons',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.navigateToHome(), onTap: () async => await _start(viewModel),
); );
} }

View File

@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/views/learn/learn_view.dart'; import 'package:yimaru_app/ui/views/learn/learn_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
import 'package:yimaru_app/ui/widgets/coming_soon.dart'; import 'package:yimaru_app/ui/widgets/coming_soon.dart';
import 'home_viewmodel.dart'; import 'home_viewmodel.dart';
@ -22,18 +23,26 @@ class HomeView extends StackedView<HomeViewModel> {
@override @override
Widget builder( Widget builder(
BuildContext context, HomeViewModel viewModel, Widget? child) => BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffold(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(HomeViewModel viewModel) => viewModel.isBusy
? const StartupView(
label: 'Checking user info',
)
: _buildScaffold(viewModel);
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold( Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentIndex), body: getViewForIndex(viewModel.currentIndex),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: _buildBottomNav(viewModel),
);
Widget _buildBottomNav(HomeViewModel viewModel) => BottomNavigationBar(
onTap: viewModel.setCurrentIndex, onTap: viewModel.setCurrentIndex,
items: _buildNavBarItems(), items: _buildNavBarItems(),
selectedItemColor: kcPrimaryColor, selectedItemColor: kcPrimaryColor,
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,
currentIndex: viewModel.currentIndex, currentIndex: viewModel.currentIndex,
),
); );
List<BottomNavigationBarItem> _buildNavBarItems() => [ List<BottomNavigationBarItem> _buildNavBarItems() => [

View File

@ -3,6 +3,7 @@ import 'package:yimaru_app/app/app.dialogs.dart';
import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/user_model.dart'; import 'package:yimaru_app/models/user_model.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/app_strings.dart'; import 'package:yimaru_app/ui/common/app_strings.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@ -14,6 +15,7 @@ import '../../common/enmus.dart';
class HomeViewModel extends BaseViewModel { class HomeViewModel extends BaseViewModel {
final _apiService = locator<ApiService>(); final _apiService = locator<ApiService>();
final _dialogService = locator<DialogService>(); final _dialogService = locator<DialogService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>(); final _bottomSheetService = locator<BottomSheetService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@ -30,16 +32,16 @@ class HomeViewModel extends BaseViewModel {
void showDialog() { void showDialog() {
_dialogService.showCustomDialog( _dialogService.showCustomDialog(
variant: DialogType.infoAlert,
title: 'Stacked Rocks!', title: 'Stacked Rocks!',
variant: DialogType.infoAlert,
description: 'Give stacked stars on Github', description: 'Give stacked stars on Github',
); );
} }
void showBottomSheet() { void showBottomSheet() {
_bottomSheetService.showCustomSheet( _bottomSheetService.showCustomSheet(
variant: BottomSheetType.notice,
title: ksHomeBottomSheetTitle, title: ksHomeBottomSheetTitle,
variant: BottomSheetType.notice,
description: ksHomeBottomSheetDescription, description: ksHomeBottomSheetDescription,
); );
} }
@ -50,13 +52,27 @@ class HomeViewModel extends BaseViewModel {
// Remote api calls // Remote api calls
Future<void> getProfileStatus() async { Future<void> getProfileStatus() async {
UserModel user = await _authenticationService.getUser(); Map<String, dynamic> response =
await runBusyFuture<Map<String, dynamic>>(_getProfileStatus());
Map<String, dynamic> response = await runBusyFuture<Map<String, dynamic>>(
_apiService.getProfileStatus(user));
if (response['status'] == ResponseStatus.success && !response['data']) { if (response['status'] == ResponseStatus.success && !response['data']) {
await replaceWithOnboarding(); await replaceWithOnboarding();
} }
} }
Future<Map<String, dynamic>> _getProfileStatus() async {
Map<String, dynamic> response = {};
UserModel user = await _authenticationService.getUser();
if (user.profileCompleted == null) {
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileStatus(user);
} else {
response = {'data': false, 'status': ResponseStatus.success};
}
} else {
response = {'data': true, 'status': ResponseStatus.success};
}
return response;
}
} }

View File

@ -3,13 +3,11 @@ import 'package:flutter_timer_countdown/flutter_timer_countdown.dart';
import 'package:pinput/pinput.dart'; import 'package:pinput/pinput.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/register/register_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_cursor.dart'; import 'package:yimaru_app/ui/widgets/custom_cursor.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/large_app_bar.dart';
import '../login_viewmodel.dart'; import '../login_viewmodel.dart';
import '../login_view.form.dart'; import '../login_view.form.dart';

View File

@ -174,15 +174,14 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: (viewModel.focusEmail && onTap: emailController.text.isNotEmpty &&
emailController.text.isNotEmpty) && passwordController.text.isNotEmpty
(viewModel.focusPassword && passwordController.text.isNotEmpty)
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: (viewModel.focusEmail && emailController.text.isNotEmpty) &&
(viewModel.focusPassword && passwordController.text.isNotEmpty)
? () async => await _login(viewModel) ? () async => await _login(viewModel)
: null, : null,
backgroundColor: emailController.text.isNotEmpty &&
passwordController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
); );
Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildOptionTextDivider() => const OptionTextDivider();

View File

@ -13,6 +13,7 @@ import '../login_view.form.dart';
class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> { class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
final TextEditingController phoneNumberController; final TextEditingController phoneNumberController;
const LoginWithPhoneNumberScreen( const LoginWithPhoneNumberScreen(
{super.key, required this.phoneNumberController}); {super.key, required this.phoneNumberController});
@ -68,10 +69,12 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount( Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
onTap: () async => await viewModel.navigateToRegister(), onTap: () async => await viewModel.navigateToRegister(),
); );
Widget _buildSubtitle() => const Text( Widget _buildSubtitle() => const Text(
'Enter your phone number. We will send you a confirmation code there', 'Enter your phone number. We will send you a confirmation code there',
style: TextStyle(color: kcMediumGrey), style: TextStyle(color: kcMediumGrey),
); );
Widget _buildPhoneNumberWrapper(LoginViewModel viewModel) => Row( Widget _buildPhoneNumberWrapper(LoginViewModel viewModel) => Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPhoneNumberChildren(viewModel), children: _buildPhoneNumberChildren(viewModel),
@ -133,12 +136,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: onTap: phoneNumberController.text.isNotEmpty
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
? () => viewModel.goTo(2) ? () => viewModel.goTo(2)
: null, : null,
backgroundColor: backgroundColor: phoneNumberController.text.isNotEmpty
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
); );

View File

@ -1,30 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_completion_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_failure_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_intro_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/birthday_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_result_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/first_assessment_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/country_region_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/fourth_assessment_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/result_analysis_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/full_name_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/retake_assessment_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/gender_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/second_assessment_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/start_lesson_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/third_assessment_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/age_group_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/challenge_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/country_region_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/educational_background_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/full_name_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/learning_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/learning_reason_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/occupation_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/forms/topic_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/language_selector.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/welcome/first_welcome_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/welcome/second_welcome_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/welcome/third_welcome_screen.dart';
import '../../common/validators/form_validator.dart'; import '../../common/validators/form_validator.dart';
import 'onboarding_viewmodel.dart'; import 'onboarding_viewmodel.dart';
@ -35,7 +23,7 @@ import 'onboarding_view.form.dart';
FormTextField(name: 'fullName', validator: FormValidator.validateForm), FormTextField(name: 'fullName', validator: FormValidator.validateForm),
FormTextField(name: 'challenge', validator: FormValidator.validateForm), FormTextField(name: 'challenge', validator: FormValidator.validateForm),
FormTextField(name: 'occupation', validator: FormValidator.validateForm), FormTextField(name: 'occupation', validator: FormValidator.validateForm),
FormTextField(name: 'learningReason', validator: FormValidator.validateForm), FormTextField(name: 'languageGoal', validator: FormValidator.validateForm),
FormTextField(name: 'topic', validator: FormValidator.validateForm), FormTextField(name: 'topic', validator: FormValidator.validateForm),
]) ])
class OnboardingView extends StackedView<OnboardingViewModel> class OnboardingView extends StackedView<OnboardingViewModel>
@ -70,8 +58,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) => Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
PopScope( PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (value, data) => viewModel.pop( onPopInvokedWithResult: (value, data) => viewModel.pop(),
language: viewModel.currentPage == 23 ? true : false),
child: _buildOnboardingScreens(viewModel)); child: _buildOnboardingScreens(viewModel));
Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack( Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
@ -80,46 +67,31 @@ class OnboardingView extends StackedView<OnboardingViewModel>
); );
List<Widget> _buildScreens() => [ List<Widget> _buildScreens() => [
_buildFirstWelcome(),
_buildSecondWelcome(),
_buildThirdWelcome(),
_buildFullNameForm(), _buildFullNameForm(),
_buildEducationalBackgroundForm(), _buildGenderForm(),
_buildBirthdayForm(),
_buildAgeGroupForm(), _buildAgeGroupForm(),
_buildEducationalBackgroundForm(),
_buildOccupationForm(), _buildOccupationForm(),
_buildCountryRegionForm(), _buildCountryRegionForm(),
_buildLearningGoalForm(), _buildLearningGoalForm(),
_buildLearningReasonForm(), _buildLanguageGoalForm(),
_buildChallengeForm(), _buildChallengeForm(),
_buildTopicForm(), _buildTopicForm(),
_buildAssessmentIntro(),
_buildFirstAssessmentForm(),
_buildSecondAssessment(),
_buildThirdAssessment(),
_buildFourthAssessment(),
_buildAssessmentFailure(),
_buildRetakeAssessment(),
_buildResultAnalysis(),
_buildAssessmentCompletion(),
_buildAssessmentResult(),
_buildStartLesson(),
_buildLanguageSelector()
]; ];
Widget _buildFirstWelcome() => const FirstWelcomeScreen();
Widget _buildSecondWelcome() => const SecondWelcomeScreen();
Widget _buildThirdWelcome() => const ThirdWelcomeScreen();
Widget _buildFullNameForm() => Widget _buildFullNameForm() =>
FullNameFormScreen(fullNameController: fullNameController); FullNameFormScreen(fullNameController: fullNameController);
Widget _buildEducationalBackgroundForm() => Widget _buildGenderForm() => const GenderFormScreen();
const EducationalBackgroundFormScreen();
Widget _buildBirthdayForm() => const BirthdayFormScreen();
Widget _buildAgeGroupForm() => const AgeGroupFormScreen(); Widget _buildAgeGroupForm() => const AgeGroupFormScreen();
Widget _buildEducationalBackgroundForm() =>
const EducationalBackgroundFormScreen();
Widget _buildOccupationForm() => Widget _buildOccupationForm() =>
OccupationFormScreen(occupationController: occupationController); OccupationFormScreen(occupationController: occupationController);
@ -127,36 +99,11 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildLearningGoalForm() => const LearningGoalFormScreen(); Widget _buildLearningGoalForm() => const LearningGoalFormScreen();
Widget _buildLearningReasonForm() => LearningReasonFormScreen( Widget _buildLanguageGoalForm() =>
learningReasonController: learningReasonController); LanguageGoalFormScreen(languageGoalController: languageGoalController);
Widget _buildChallengeForm() => Widget _buildChallengeForm() =>
ChallengeFormScreen(challengeController: challengeController); ChallengeFormScreen(challengeController: challengeController);
Widget _buildTopicForm() => TopicFormScreen(topicController: topicController); Widget _buildTopicForm() => TopicFormScreen(topicController: topicController);
Widget _buildAssessmentIntro() => const AssessmentIntroScreen();
Widget _buildFirstAssessmentForm() =>
FirstAssessmentFormScreen(answerController: answerController);
Widget _buildSecondAssessment() => const SecondAssessmentFormScreen();
Widget _buildThirdAssessment() => const ThirdAssessmentFormScreen();
Widget _buildFourthAssessment() => const FourthAssessmentFormScreen();
Widget _buildAssessmentFailure() => const AssessmentFailureScreen();
Widget _buildRetakeAssessment() => const RetakeAssessmentScreen();
Widget _buildResultAnalysis() => const ResultAnalysisScreen();
Widget _buildAssessmentCompletion() => const AssessmentCompletionScreen();
Widget _buildAssessmentResult() => const AssessmentResultScreen();
Widget _buildStartLesson() => const StartLessonScreen();
Widget _buildLanguageSelector() => const LanguageSelector();
} }

View File

@ -16,7 +16,7 @@ const String AnswerValueKey = 'answer';
const String FullNameValueKey = 'fullName'; const String FullNameValueKey = 'fullName';
const String ChallengeValueKey = 'challenge'; const String ChallengeValueKey = 'challenge';
const String OccupationValueKey = 'occupation'; const String OccupationValueKey = 'occupation';
const String LearningReasonValueKey = 'learningReason'; const String LanguageGoalValueKey = 'languageGoal';
const String TopicValueKey = 'topic'; const String TopicValueKey = 'topic';
final Map<String, TextEditingController> _OnboardingViewTextEditingControllers = final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
@ -29,7 +29,7 @@ final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
FullNameValueKey: FormValidator.validateForm, FullNameValueKey: FormValidator.validateForm,
ChallengeValueKey: FormValidator.validateForm, ChallengeValueKey: FormValidator.validateForm,
OccupationValueKey: FormValidator.validateForm, OccupationValueKey: FormValidator.validateForm,
LearningReasonValueKey: FormValidator.validateForm, LanguageGoalValueKey: FormValidator.validateForm,
TopicValueKey: FormValidator.validateForm, TopicValueKey: FormValidator.validateForm,
}; };
@ -42,8 +42,8 @@ mixin $OnboardingView {
_getFormTextEditingController(ChallengeValueKey); _getFormTextEditingController(ChallengeValueKey);
TextEditingController get occupationController => TextEditingController get occupationController =>
_getFormTextEditingController(OccupationValueKey); _getFormTextEditingController(OccupationValueKey);
TextEditingController get learningReasonController => TextEditingController get languageGoalController =>
_getFormTextEditingController(LearningReasonValueKey); _getFormTextEditingController(LanguageGoalValueKey);
TextEditingController get topicController => TextEditingController get topicController =>
_getFormTextEditingController(TopicValueKey); _getFormTextEditingController(TopicValueKey);
@ -51,8 +51,8 @@ mixin $OnboardingView {
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey); FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey); FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
FocusNode get learningReasonFocusNode => FocusNode get languageGoalFocusNode =>
_getFormFocusNode(LearningReasonValueKey); _getFormFocusNode(LanguageGoalValueKey);
FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey); FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey);
TextEditingController _getFormTextEditingController( TextEditingController _getFormTextEditingController(
@ -83,7 +83,7 @@ mixin $OnboardingView {
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model));
learningReasonController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model));
topicController.addListener(() => _updateFormData(model)); topicController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
@ -100,7 +100,7 @@ mixin $OnboardingView {
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model)); challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model)); occupationController.addListener(() => _updateFormData(model));
learningReasonController.addListener(() => _updateFormData(model)); languageGoalController.addListener(() => _updateFormData(model));
topicController.addListener(() => _updateFormData(model)); topicController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
@ -115,7 +115,7 @@ mixin $OnboardingView {
FullNameValueKey: fullNameController.text, FullNameValueKey: fullNameController.text,
ChallengeValueKey: challengeController.text, ChallengeValueKey: challengeController.text,
OccupationValueKey: occupationController.text, OccupationValueKey: occupationController.text,
LearningReasonValueKey: learningReasonController.text, LanguageGoalValueKey: languageGoalController.text,
TopicValueKey: topicController.text, TopicValueKey: topicController.text,
}), }),
); );
@ -163,8 +163,8 @@ extension ValueProperties on FormStateHelper {
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?; String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
String? get occupationValue => String? get occupationValue =>
this.formValueMap[OccupationValueKey] as String?; this.formValueMap[OccupationValueKey] as String?;
String? get learningReasonValue => String? get languageGoalValue =>
this.formValueMap[LearningReasonValueKey] as String?; this.formValueMap[LanguageGoalValueKey] as String?;
String? get topicValue => this.formValueMap[TopicValueKey] as String?; String? get topicValue => this.formValueMap[TopicValueKey] as String?;
set answerValue(String? value) { set answerValue(String? value) {
@ -210,14 +210,14 @@ extension ValueProperties on FormStateHelper {
} }
} }
set learningReasonValue(String? value) { set languageGoalValue(String? value) {
this.setData( this.setData(
this.formValueMap..addAll({LearningReasonValueKey: value}), this.formValueMap..addAll({LanguageGoalValueKey: value}),
); );
if (_OnboardingViewTextEditingControllers.containsKey( if (_OnboardingViewTextEditingControllers.containsKey(
LearningReasonValueKey)) { LanguageGoalValueKey)) {
_OnboardingViewTextEditingControllers[LearningReasonValueKey]?.text = _OnboardingViewTextEditingControllers[LanguageGoalValueKey]?.text =
value ?? ''; value ?? '';
} }
} }
@ -244,9 +244,9 @@ extension ValueProperties on FormStateHelper {
bool get hasOccupation => bool get hasOccupation =>
this.formValueMap.containsKey(OccupationValueKey) && this.formValueMap.containsKey(OccupationValueKey) &&
(occupationValue?.isNotEmpty ?? false); (occupationValue?.isNotEmpty ?? false);
bool get hasLearningReason => bool get hasLanguageGoal =>
this.formValueMap.containsKey(LearningReasonValueKey) && this.formValueMap.containsKey(LanguageGoalValueKey) &&
(learningReasonValue?.isNotEmpty ?? false); (languageGoalValue?.isNotEmpty ?? false);
bool get hasTopic => bool get hasTopic =>
this.formValueMap.containsKey(TopicValueKey) && this.formValueMap.containsKey(TopicValueKey) &&
(topicValue?.isNotEmpty ?? false); (topicValue?.isNotEmpty ?? false);
@ -259,9 +259,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false;
bool get hasOccupationValidationMessage => bool get hasOccupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false;
bool get hasLearningReasonValidationMessage => bool get hasLanguageGoalValidationMessage =>
this.fieldsValidationMessages[LearningReasonValueKey]?.isNotEmpty ?? this.fieldsValidationMessages[LanguageGoalValueKey]?.isNotEmpty ?? false;
false;
bool get hasTopicValidationMessage => bool get hasTopicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
@ -273,8 +272,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey]; this.fieldsValidationMessages[ChallengeValueKey];
String? get occupationValidationMessage => String? get occupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey]; this.fieldsValidationMessages[OccupationValueKey];
String? get learningReasonValidationMessage => String? get languageGoalValidationMessage =>
this.fieldsValidationMessages[LearningReasonValueKey]; this.fieldsValidationMessages[LanguageGoalValueKey];
String? get topicValidationMessage => String? get topicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey]; this.fieldsValidationMessages[TopicValueKey];
} }
@ -288,8 +287,8 @@ extension Methods on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage; this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
setOccupationValidationMessage(String? validationMessage) => setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage; this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
setLearningReasonValidationMessage(String? validationMessage) => setLanguageGoalValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LearningReasonValueKey] = validationMessage; this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage;
setTopicValidationMessage(String? validationMessage) => setTopicValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[TopicValueKey] = validationMessage; this.fieldsValidationMessages[TopicValueKey] = validationMessage;
@ -299,7 +298,7 @@ extension Methods on FormStateHelper {
fullNameValue = ''; fullNameValue = '';
challengeValue = ''; challengeValue = '';
occupationValue = ''; occupationValue = '';
learningReasonValue = ''; languageGoalValue = '';
topicValue = ''; topicValue = '';
} }
@ -310,7 +309,7 @@ extension Methods on FormStateHelper {
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey),
LearningReasonValueKey: getValidationMessage(LearningReasonValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
TopicValueKey: getValidationMessage(TopicValueKey), TopicValueKey: getValidationMessage(TopicValueKey),
}); });
} }
@ -335,6 +334,6 @@ void updateValidationData(FormStateHelper model) =>
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey), ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey), OccupationValueKey: getValidationMessage(OccupationValueKey),
LearningReasonValueKey: getValidationMessage(LearningReasonValueKey), LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
TopicValueKey: getValidationMessage(TopicValueKey), TopicValueKey: getValidationMessage(TopicValueKey),
}); });

View File

@ -2,6 +2,7 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../home/home_view.dart';
class OnboardingViewModel extends FormViewModel { class OnboardingViewModel extends FormViewModel {
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
@ -34,6 +35,23 @@ class OnboardingViewModel extends FormViewModel {
String? get selectedEducationalBackground => _selectedEducationalBackground; String? get selectedEducationalBackground => _selectedEducationalBackground;
// Gender
final List<String> _genders = [
'Male',
'Female',
];
List<String> get genders => _genders;
String? _selectedGender;
String? get selectedGender => _selectedGender;
// Birthday
String? _selectedBirthday;
String? get selectedBirthday => _selectedBirthday;
// Age group // Age group
final List<String> _ageGroups = [ final List<String> _ageGroups = [
'8-14', '8-14',
@ -53,6 +71,36 @@ class OnboardingViewModel extends FormViewModel {
bool get focusOccupation => _focusOccupation; bool get focusOccupation => _focusOccupation;
// Country
String _selectedCountry = 'Ethiopia';
String get selectedCountry => _selectedCountry;
Future<List<String>> getCountries() async => ['Ethiopia'];
// Country
String _selectedRegion = 'Addis Ababa';
String get selectedRegion => _selectedRegion;
Future<List<String>> getRegions(String country) async =>
[ 'Afar',
'SNNPR',
'Amhara',
'Harari',
'Oromia',
'Sidama',
'Somali',
'Tigray',
'Gambela',
'Dire Dawa',
'Addis Ababa',
'Central Ethiopia',
'Benishangul-Gumuz',
'South West Ethiopia',
];
// Learning goal // Learning goal
String? _selectedLearningGoal; String? _selectedLearningGoal;
@ -79,19 +127,19 @@ class OnboardingViewModel extends FormViewModel {
List<Map<String, dynamic>> get learningGoals => _learningGoals; List<Map<String, dynamic>> get learningGoals => _learningGoals;
// Learning reason // Learning reason
bool _showReasonTextBox = false; bool _showLanguageGoalTextBox = false;
bool get showReasonTextBox => _showReasonTextBox; bool get showLanguageGoalTextBox => _showLanguageGoalTextBox;
bool _focusLearningReason = false; bool _focusLanguageGoal = false;
bool get focusLearningReason => _focusLearningReason; bool get focusLanguageGoal => _focusLanguageGoal;
String? _selectedLearningReason; String? _selectedLanguageGoal;
String? get selectedLearningReason => _selectedLearningReason; String? get selectedLanguageGoal => _selectedLanguageGoal;
final List<String> _learningReasons = [ final List<String> _languageGoals = [
'Speak confidently at work or school', 'Speak confidently at work or school',
'Travel or handle daily situations', 'Travel or handle daily situations',
'Connect with family or friends', 'Connect with family or friends',
@ -99,7 +147,7 @@ class OnboardingViewModel extends FormViewModel {
'Other' 'Other'
]; ];
List<String> get learningReasons => _learningReasons; List<String> get languageGoals => _languageGoals;
// Challenges // Challenges
bool _showChallengeTextBox = false; bool _showChallengeTextBox = false;
@ -147,53 +195,6 @@ class OnboardingViewModel extends FormViewModel {
List<String> get topics => _topics; List<String> get topics => _topics;
// First assessment
bool _focusFirstAssessment = false;
bool get focusFirstAssessment => _focusFirstAssessment;
// Second assessment
final List<String> _secondAnswers = [
'go',
'goes',
'went',
'going',
];
List<String> get secondAnswers => _secondAnswers;
String? _selectedA2Answer;
String? get selectedA2Answer => _selectedA2Answer;
// Third assessment
final List<String> _thirdAnswers = [
'reduce',
'grow',
'stop',
'hide',
];
List<String> get thirdAnswers => _thirdAnswers;
String? _selectedA3Answer;
String? get selectedA3Answer => _selectedA3Answer;
// Third assessment
final List<String> _fourthAnswers = [
'reduce',
'grow',
'stop',
'hide',
];
List<String> get fourthAnswers => _fourthAnswers;
String? _selectedA4Answer;
String? get selectedA4Answer => _selectedA4Answer;
// Languages // Languages
final List<Map<String, dynamic>> _languages = [ final List<Map<String, dynamic>> _languages = [
{'code': 'አማ', 'language': 'አማርኛ'}, {'code': 'አማ', 'language': 'አማርኛ'},
@ -209,6 +210,11 @@ class OnboardingViewModel extends FormViewModel {
Map<String, dynamic> get selectedLanguage => _selectedLanguage; Map<String, dynamic> get selectedLanguage => _selectedLanguage;
// User data
final Map<String, dynamic> _userData = {};
Map<String, dynamic> get userData => _userData;
// Full name // Full name
void setFullNameFocus() { void setFullNameFocus() {
_focusFullName = true; _focusFullName = true;
@ -216,21 +222,35 @@ class OnboardingViewModel extends FormViewModel {
} }
// Education background // Education background
void setSelectedEducationalBackground(String title) { void setSelectedEducationalBackground(String value) {
_selectedEducationalBackground = title; _selectedEducationalBackground = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedEducationalBackground(String title) => bool isSelectedEducationalBackground(String value) =>
_selectedEducationalBackground == title; _selectedEducationalBackground == value;
// Gender
void setSelectedGender(String gender) {
_selectedGender = gender;
rebuildUi();
}
bool isSelectedGender(String value) => _selectedGender == value;
// Birthday
void setBirthday(String value) {
_selectedBirthday = value;
rebuildUi();
}
// Age group // Age group
void setSelectedAgeGroup(String title) { void setSelectedAgeGroup(String value) {
_selectedAgeGroup = title; _selectedAgeGroup = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedAgeGroup(String title) => _selectedAgeGroup == title; bool isSelectedAgeGroup(String value) => _selectedAgeGroup == value;
// Occupation // Occupation
void setOccupationFocus() { void setOccupationFocus() {
@ -239,41 +259,45 @@ class OnboardingViewModel extends FormViewModel {
} }
// Country // Country
Future<List<String>> getCountries() async => ['Ethiopia', 'Djibouti']; void setSelectedCountry(String value) {
_selectedCountry = value;
rebuildUi();
}
// Region // Region
Future<List<String>> getRegions(String country) async => void setSelectedRegion(String value) {
['Addis Ababa', 'Oromia']; _selectedRegion = value;
rebuildUi();
}
// Learning goal // Learning goal
void setSelectedLearningGoal(String title) { void setSelectedLearningGoal(String value) {
_selectedLearningGoal = title; _selectedLearningGoal = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedLearningGoal(String title) => _selectedLearningGoal == title; bool isSelectedLearningGoal(String value) => _selectedLearningGoal == value;
// Learning reason // Learning reason
void setLearningReasonFocus() { void setLanguageGoalFocus() {
_focusLearningReason = true; _focusLanguageGoal = true;
rebuildUi(); rebuildUi();
} }
void setSelectedLearningReason(String title) { void setSelectedLanguageGoal(String value) {
_selectedLearningReason = title; _selectedLanguageGoal = value;
if (title.toLowerCase() == 'other') { if (value.toLowerCase() == 'other') {
_showReasonTextBox = true; _showLanguageGoalTextBox = true;
} else { } else {
if (_showReasonTextBox) { if (_showLanguageGoalTextBox) {
_showReasonTextBox = false; _showLanguageGoalTextBox = false;
_focusLearningReason = false; _focusLanguageGoal = false;
} }
} }
rebuildUi(); rebuildUi();
} }
bool isSelectedLearningReason(String title) => bool isSelectedLanguageGoal(String value) => _selectedLanguageGoal == value;
_selectedLearningReason == title;
// Challenges // Challenges
void setChallengesFocus() { void setChallengesFocus() {
@ -281,9 +305,9 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi(); rebuildUi();
} }
void setSelectedChallenge(String title) { void setSelectedChallenge(String value) {
_selectedChallenge = title; _selectedChallenge = value;
if (title.toLowerCase() == 'other') { if (value.toLowerCase() == 'other') {
_showChallengeTextBox = true; _showChallengeTextBox = true;
} else { } else {
if (_showChallengeTextBox) { if (_showChallengeTextBox) {
@ -294,7 +318,7 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi(); rebuildUi();
} }
bool isSelectedChallenge(String title) => _selectedChallenge == title; bool isSelectedChallenge(String value) => _selectedChallenge == value;
// Topics // Topics
void setTopicsFocus() { void setTopicsFocus() {
@ -302,9 +326,9 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi(); rebuildUi();
} }
void setSelectedTopic(String title) { void setSelectedTopic(String value) {
_selectedTopic = title; _selectedTopic = value;
if (title.toLowerCase() == 'other') { if (value.toLowerCase() == 'other') {
_showTopicTextBox = true; _showTopicTextBox = true;
} else { } else {
if (_showTopicTextBox) { if (_showTopicTextBox) {
@ -315,50 +339,37 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi(); rebuildUi();
} }
bool isSelectedTopic(String title) => _selectedTopic == title; bool isSelectedTopic(String value) => _selectedTopic == value;
// First assessment
void setFirstAssessmentFocus() {
_focusFirstAssessment = true;
rebuildUi();
}
// Second assessment
void setSelectedA2Answer(String title) {
_selectedA2Answer = title;
rebuildUi();
}
bool isSelectedA2Answer(String title) => _selectedA2Answer == title;
// Third assessment
void setSelectedA3Answer(String title) {
_selectedA3Answer = title;
rebuildUi();
}
bool isSelectedA3Answer(String title) => _selectedA3Answer == title;
// Fourth assessment
void setSelectedA4Answer(String title) {
_selectedA4Answer = title;
rebuildUi();
}
bool isSelectedA4Answer(String title) => _selectedA4Answer == title;
// Language // Language
void setSelectedLanguage(Map<String, dynamic> title) { void setSelectedLanguage(Map<String, dynamic> value) {
_selectedLanguage = title; _selectedLanguage = value;
rebuildUi(); rebuildUi();
} }
bool isSelectedLanguage(String title) => bool isSelectedLanguage(String value) =>
_selectedLanguage['language'] == title; _selectedLanguage['language'] == value;
// Add user data
void addUserData(Map<String, dynamic> data) {
_userData.addAll(data);
print('User data : $_userData');
}
void clearUserData() {
_userData.clear();
}
// Navigation // Navigation
Future<void> navigateToHome() async =>
await _navigationService.navigateToHomeView(); Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView();
Future<void> navigateToAssessment() async =>
await _navigationService.navigateToAssessmentView(data: _userData);
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView());
void next({int? page}) async { void next({int? page}) async {
if (page == null) { if (page == null) {
@ -366,11 +377,6 @@ class OnboardingViewModel extends FormViewModel {
_currentPage = _previousPage; _currentPage = _previousPage;
} else { } else {
_currentPage++; _currentPage++;
if (_currentPage == 19) {
rebuildUi();
await Future.delayed(const Duration(seconds: 3));
_currentPage++;
}
} }
} else { } else {
_previousPage = _currentPage; _previousPage = _currentPage;
@ -379,14 +385,13 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi(); rebuildUi();
} }
void pop({bool language = false}) { void pop() {
if (!language) { if (_currentPage == 8) {
_currentPage--; _navigationService.back();
} else { } else {
_currentPage = _previousPage; _currentPage--;
_previousPage = 0;
}
rebuildUi(); rebuildUi();
} }
} }
}

View File

@ -10,6 +10,15 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> { class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
const AgeGroupFormScreen({super.key}); const AgeGroupFormScreen({super.key});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'age_group': viewModel.selectedAgeGroup};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -34,6 +43,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(viewModel), child: _buildBodyWrapper(viewModel),
); );
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
@ -67,10 +77,8 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Which age range are you in?', 'Which age range are you in?',
@ -123,6 +131,6 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap: onTap:
viewModel.selectedAgeGroup != null ? () => viewModel.next() : null, viewModel.selectedAgeGroup != null ? () => _next(viewModel) : null,
); );
} }

View File

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../onboarding_view.form.dart';
class FirstAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController answerController;
const FirstAssessmentFormScreen({super.key, required this.answerController});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceLarge,
_buildFirstAssessmentFormField(viewModel),
if (viewModel.hasAnswerValidationMessage &&
viewModel.focusFirstAssessment)
verticalSpaceTiny,
if (viewModel.hasAnswerValidationMessage &&
viewModel.focusFirstAssessment)
_buildFirstAssessmentValidatorWrapper(viewModel)
];
Widget _buildAppBar() => const LargeAppBar(
showBackButton: false,
showLanguageSelection: false,
);
Widget _buildTitle() => const Text(
'1. What is the plural of “book”?',
style: TextStyle(
fontSize: 16,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
);
Widget _buildFirstAssessmentFormField(OnboardingViewModel viewModel) =>
TextFormField(
controller: answerController,
onTap: viewModel.setFirstAssessmentFocus,
decoration: inputDecoration(
focus: viewModel.focusFirstAssessment,
filled: answerController.text.isNotEmpty),
);
Widget _buildFirstAssessmentValidatorWrapper(OnboardingViewModel viewModel) =>
viewModel.hasAnswerValidationMessage
? _buildFirstAssessmentValidator(viewModel)
: Container();
Widget _buildFirstAssessmentValidator(OnboardingViewModel viewModel) => Text(
viewModel.answerValidationMessage!,
style: const TextStyle(
fontSize: 12,
color: Colors.red,
fontWeight: FontWeight.w700,
),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: answerController.text.isNotEmpty
? kcPrimaryColor
: viewModel.focusFirstAssessment && answerController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: answerController.text.isNotEmpty
? () => viewModel.next()
: viewModel.focusFirstAssessment && answerController.text.isNotEmpty
? () => viewModel.next()
: null,
);
}

View File

@ -1,113 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class SecondAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
const SecondAssessmentFormScreen({super.key});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceMedium,
_buildAnswers(viewModel)
];
Widget _buildAppBar() => const LargeAppBar(
showBackButton: false,
showLanguageSelection: false,
);
Widget _buildTitle() => const Text(
'Q2. Choose the correct word to complete the sentence:\nI ____ to school yesterday. ',
style: TextStyle(
fontSize: 16,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
);
Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.secondAnswers.length,
itemBuilder: (context, index) => _buildAnswer(
title: viewModel.secondAnswers[index],
selected:
viewModel.isSelectedA2Answer(viewModel.secondAnswers[index]),
onTap: () =>
viewModel.setSelectedA2Answer(viewModel.secondAnswers[index]),
),
);
Widget _buildAnswer(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
CustomSmallRadioButton(
title: title,
onTap: onTap,
selected: selected,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap:
viewModel.selectedA2Answer != null ? () => viewModel.next() : null,
backgroundColor: viewModel.selectedA2Answer != null
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
);
}

View File

@ -1,14 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> { import '../../../widgets/birthday_selector.dart';
const FourthAssessmentFormScreen({super.key});
class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
const BirthdayFormScreen({super.key});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'birth_day': DateFormat('yyyy-MM-dd')
.parseUTC(viewModel.selectedBirthday ?? DateTime.now().toString())
.toIso8601String()
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -25,7 +39,7 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
@ -53,45 +67,38 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAnswers(viewModel) _buildBirthdayFormField(viewModel)
]; ];
Widget _buildAppBar() => const LargeAppBar( Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: false, onPop: viewModel.pop,
showLanguageSelection: false, showBackButton: true,
showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(),
); );
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Q4.  Choose the word that best matches the meaning of meticulous:', 'Pick your birthday?',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 25,
color: kcDarkGrey, color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
); );
Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder( Widget _buildSubTitle() => const Text(
shrinkWrap: true, 'Well personalize your learning experience based on your birthday.',
itemCount: viewModel.fourthAnswers.length, style: TextStyle(color: kcMediumGrey),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAnswer(
title: viewModel.fourthAnswers[index],
selected:
viewModel.isSelectedA4Answer(viewModel.fourthAnswers[index]),
onTap: () =>
viewModel.setSelectedA4Answer(viewModel.fourthAnswers[index]),
),
); );
Widget _buildAnswer( Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
{required String title, BirthdaySelector(
required bool selected, birthday: viewModel.selectedBirthday,
required GestureTapCallback onTap}) => onSelected: (value) =>
CustomSmallRadioButton( viewModel.setBirthday(DateFormat('yyyy-MM-dd').format(value)),
title: title,
onTap: onTap,
selected: selected,
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
@ -105,10 +112,10 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: backgroundColor: viewModel.selectedBirthday != null
viewModel.selectedA4Answer != null ? () => viewModel.next() : null,
backgroundColor: viewModel.selectedA4Answer != null
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap:
viewModel.selectedBirthday != null ? () => _next(viewModel) : null,
); );
} }

View File

@ -13,6 +13,18 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
const ChallengeFormScreen({super.key, required this.challengeController}); const ChallengeFormScreen({super.key, required this.challengeController});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'language_challange':
viewModel.selectedChallenge ?? challengeController.text,
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -37,6 +49,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(viewModel), child: _buildBodyWrapper(viewModel),
); );
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
@ -80,10 +93,8 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'What challenge do you face most with English?', 'What challenge do you face most with English?',
@ -160,15 +171,14 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.selectedChallenge != null onTap: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other' ? viewModel.selectedChallenge?.toLowerCase() == 'other'
? viewModel.focusChallenge ? challengeController.text.isNotEmpty
? () => viewModel.next() ? () => _next(viewModel)
: null : null
: () => viewModel.next() : () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedChallenge != null backgroundColor: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other' ? viewModel.selectedChallenge?.toLowerCase() == 'other'
? viewModel.focusChallenge && ? challengeController.text.isNotEmpty
challengeController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1) : kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor : kcPrimaryColor

View File

@ -10,6 +10,18 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> { class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
const CountryRegionFormScreen({super.key}); const CountryRegionFormScreen({super.key});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'country': viewModel.selectedCountry,
'region': viewModel.selectedRegion
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -71,10 +83,8 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Where are you from?', 'Where are you from?',
@ -92,19 +102,20 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildCountryDropDown(OnboardingViewModel viewModel) => Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
CustomDropdownPicker( CustomDropdownPicker(
onChanged: (value) {},
hint: 'Select country', hint: 'Select country',
icon: _buildSearchIcon(), icon: _buildSearchIcon(),
selectedItem: 'Ethiopia', selectedItem: 'Ethiopia',
items: (value, props) => viewModel.getCountries(), items: (value, props) => viewModel.getCountries(),
); onChanged: (value) =>
viewModel.setSelectedCountry(value ?? 'Ethiopia'));
Widget _buildRegionDropDown(OnboardingViewModel viewModel) => Widget _buildRegionDropDown(OnboardingViewModel viewModel) =>
CustomDropdownPicker( CustomDropdownPicker(
hint: 'Select region', hint: 'Select region',
onChanged: (value) {},
icon: _buildSearchIcon(), icon: _buildSearchIcon(),
selectedItem: 'Addis Ababa', selectedItem: 'Addis Ababa',
onChanged: (value) =>
viewModel.setSelectedRegion(value ?? 'Addis Ababa'),
items: (value, props) => viewModel.getRegions('Addis Ababa'), items: (value, props) => viewModel.getRegions('Addis Ababa'),
); );
@ -123,8 +134,8 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
height: 55, height: 55,
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
onTap: () => viewModel.next(),
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => _next(viewModel),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
} }

View File

@ -11,6 +11,17 @@ class EducationalBackgroundFormScreen
extends ViewModelWidget<OnboardingViewModel> { extends ViewModelWidget<OnboardingViewModel> {
const EducationalBackgroundFormScreen({super.key}); const EducationalBackgroundFormScreen({super.key});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'education_level': viewModel.selectedEducationalBackground
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -69,10 +80,8 @@ class EducationalBackgroundFormScreen
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Whats your current educational level?', 'Whats your current educational level?',
@ -124,7 +133,7 @@ class EducationalBackgroundFormScreen
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.selectedEducationalBackground != null onTap: viewModel.selectedEducationalBackground != null
? () => viewModel.next() ? () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedEducationalBackground != null backgroundColor: viewModel.selectedEducationalBackground != null
? kcPrimaryColor ? kcPrimaryColor

View File

@ -1,18 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/helper_functions.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../onboarding_view.form.dart'; import '../onboarding_view.form.dart';
class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> { class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController fullNameController; final TextEditingController fullNameController;
const FullNameFormScreen({super.key, required this.fullNameController}); const FullNameFormScreen({super.key, required this.fullNameController});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = splitFullName(fullNameController.text);
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -74,10 +84,8 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar( Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'What should we call you? 😊', 'What should we call you? 😊',
@ -128,11 +136,9 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.focusFullName && fullNameController.text.isNotEmpty onTap:
? () => viewModel.next() fullNameController.text.isNotEmpty ? () => _next(viewModel) : null,
: null, backgroundColor: fullNameController.text.isNotEmpty
backgroundColor:
viewModel.focusFullName && fullNameController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
); );

View File

@ -7,8 +7,17 @@ import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> { class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
const ThirdAssessmentFormScreen({super.key}); const GenderFormScreen({super.key});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'gender': viewModel.selectedGender};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -25,7 +34,7 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
@ -53,37 +62,45 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAnswers(viewModel) _buildAgeGroups(viewModel)
]; ];
Widget _buildAppBar() => const LargeAppBar( Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: false, onPop: viewModel.pop,
showLanguageSelection: false, showBackButton: true,
showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(),
); );
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Q3. Which word means the same as expand?', 'Choose your gender?',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 25,
color: kcDarkGrey, color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
); );
Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder( Widget _buildSubTitle() => const Text(
'Well personalize your learning experience based on your gender.',
style: TextStyle(color: kcMediumGrey),
);
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.thirdAnswers.length, itemCount: viewModel.genders.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAnswer( itemBuilder: (context, index) => _buildAgeGroup(
title: viewModel.thirdAnswers[index], title: viewModel.genders[index],
selected: viewModel.isSelectedA3Answer(viewModel.thirdAnswers[index]), selected: viewModel.isSelectedGender(viewModel.genders[index]),
onTap: () => onTap: () => viewModel.setSelectedGender(viewModel.genders[index]),
viewModel.setSelectedA3Answer(viewModel.thirdAnswers[index]),
), ),
); );
Widget _buildAnswer( Widget _buildAgeGroup(
{required String title, {required String title,
required bool selected, required bool selected,
required GestureTapCallback onTap}) => required GestureTapCallback onTap}) =>
@ -104,10 +121,9 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: viewModel.selectedA3Answer != null backgroundColor: viewModel.selectedGender != null
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap: onTap: viewModel.selectedGender != null ? () => _next(viewModel) : null,
viewModel.selectedA3Answer != null ? () => viewModel.next() : null,
); );
} }

View File

@ -8,11 +8,23 @@ import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> { class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController learningReasonController; final TextEditingController languageGoalController;
const LearningReasonFormScreen( const LanguageGoalFormScreen(
{super.key, required this.learningReasonController}); {super.key, required this.languageGoalController});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'language_goal':
viewModel.selectedLanguageGoal ?? languageGoalController.text,
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -66,14 +78,14 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(), _buildSubTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildReasons(viewModel), _buildReasons(viewModel),
if (viewModel.showReasonTextBox) _buildReasonFormField(viewModel), if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
if (viewModel.showReasonTextBox && if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLearningReasonValidationMessage && viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLearningReason) viewModel.focusLanguageGoal)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.showReasonTextBox && if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLearningReasonValidationMessage && viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLearningReason) viewModel.focusLanguageGoal)
_buildReasonValidatorWrapper(viewModel), _buildReasonValidatorWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
@ -82,8 +94,8 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop(language: false)); );
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Whats your main goal for improving your English?', 'Whats your main goal for improving your English?',
@ -104,18 +116,18 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder( Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
itemCount: viewModel.learningReasons.length, itemCount: viewModel.languageGoals.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildLearningReason( itemBuilder: (context, index) => _buildLanguageGoal(
title: viewModel.learningReasons[index], title: viewModel.languageGoals[index],
selected: viewModel selected:
.isSelectedLearningReason(viewModel.learningReasons[index]), viewModel.isSelectedLanguageGoal(viewModel.languageGoals[index]),
onTap: () => viewModel onTap: () =>
.setSelectedLearningReason(viewModel.learningReasons[index]), viewModel.setSelectedLanguageGoal(viewModel.languageGoals[index]),
), ),
); );
Widget _buildLearningReason( Widget _buildLanguageGoal(
{required String title, {required String title,
required bool selected, required bool selected,
required GestureTapCallback onTap}) => required GestureTapCallback onTap}) =>
@ -127,21 +139,21 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildReasonFormField(OnboardingViewModel viewModel) => TextFormField( Widget _buildReasonFormField(OnboardingViewModel viewModel) => TextFormField(
maxLines: 3, maxLines: 3,
controller: learningReasonController, controller: languageGoalController,
onTap: viewModel.setLearningReasonFocus, onTap: viewModel.setLanguageGoalFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Write your goal…', hint: 'Write your goal…',
filled: learningReasonController.text.isNotEmpty), filled: languageGoalController.text.isNotEmpty),
); );
Widget _buildReasonValidatorWrapper(OnboardingViewModel viewModel) => Widget _buildReasonValidatorWrapper(OnboardingViewModel viewModel) =>
viewModel.hasLearningReasonValidationMessage viewModel.hasLanguageGoalValidationMessage
? _buildReasonValidator(viewModel) ? _buildReasonValidator(viewModel)
: Container(); : Container();
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text( Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
viewModel.learningReasonValidationMessage!, viewModel.languageGoalValidationMessage!,
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.red, color: Colors.red,
@ -160,17 +172,16 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.selectedLearningReason != null onTap: viewModel.selectedLanguageGoal != null
? viewModel.selectedLearningReason?.toLowerCase() == 'other' ? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? viewModel.focusLearningReason ? languageGoalController.text.isNotEmpty
? () => viewModel.next() ? () => _next(viewModel)
: null : null
: () => viewModel.next() : () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedLearningReason != null backgroundColor: viewModel.selectedLanguageGoal != null
? viewModel.selectedLearningReason?.toLowerCase() == 'other' ? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? viewModel.focusLearningReason && ? languageGoalController.text.isNotEmpty
learningReasonController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1) : kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor : kcPrimaryColor

View File

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/custom_small_radio_button.dart';
import '../../../widgets/large_app_bar.dart';
class LanguageSelector extends ViewModelWidget<OnboardingViewModel> {
const LanguageSelector({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
[_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildColumnScroller(OnboardingViewModel viewModel) =>
SingleChildScrollView(
child: _buildUpperColumn(viewModel),
);
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
verticalSpaceMedium,
_buildLanguages(viewModel)
];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: true,
onPop: viewModel.pop,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: true,
));
Widget _buildTitle() => const Text(
'Choose your language',
style: TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
);
Widget _buildSubTitle() => const Text(
'You can switch languages anytime in Settings',
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLanguages(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true,
itemCount: viewModel.languages.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildLanguage(
title: viewModel.languages[index]['language'],
selected: viewModel
.isSelectedLanguage(viewModel.languages[index]['language']),
onTap: () =>
viewModel.setSelectedLanguage(viewModel.languages[index]),
),
);
Widget _buildLanguage(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
CustomSmallRadioButton(
title: title,
onTap: onTap,
selected: selected,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () => viewModel.pop(language: true),
);
}

View File

@ -23,6 +23,17 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
return Icons.book; return Icons.book;
} }
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'learning_goal': viewModel.selectedLearningGoal,
};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -47,6 +58,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(viewModel), child: _buildBodyWrapper(viewModel),
); );
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding( Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
@ -69,7 +81,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildLearningGoals(viewModel) _buildLearningGoals(viewModel)
]; ];
@ -78,14 +90,12 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop, onPop: viewModel.pop,
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle(OnboardingViewModel viewModel) => Text(
'Hi Johnny, Choose your learning goal.', 'Hi ${viewModel.userData['first_name']}, Choose your learning goal.',
style: TextStyle( style: const TextStyle(
fontSize: 25, fontSize: 25,
color: kcDarkGrey, color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -133,7 +143,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.selectedLearningGoal != null onTap: viewModel.selectedLearningGoal != null
? () => viewModel.next() ? () => _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedLearningGoal != null backgroundColor: viewModel.selectedLearningGoal != null
? kcPrimaryColor ? kcPrimaryColor

View File

@ -6,13 +6,22 @@ import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart'; import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../onboarding_view.form.dart'; import '../onboarding_view.form.dart';
class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> { class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController occupationController; final TextEditingController occupationController;
const OccupationFormScreen({super.key, required this.occupationController}); const OccupationFormScreen({super.key, required this.occupationController});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {'occupation': occupationController.text};
viewModel.addUserData(data);
viewModel.next();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -77,10 +86,8 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.pop, onPop: viewModel.pop,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23), onLanguage: () async => await viewModel.navigateToLanguage(),
onTap: () => viewModel.pop( );
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Whats your occupation?', 'Whats your occupation?',
@ -131,11 +138,10 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.focusOccupation && occupationController.text.isNotEmpty onTap: occupationController.text.isNotEmpty
? () => viewModel.next() ? () => _next(viewModel)
: null, : null,
backgroundColor: backgroundColor: occupationController.text.isNotEmpty
viewModel.focusOccupation && occupationController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
); );

View File

@ -13,6 +13,17 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
const TopicFormScreen({super.key, required this.topicController}); const TopicFormScreen({super.key, required this.topicController});
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'favoutite_topic': viewModel.selectedTopic ?? topicController.text,
};
viewModel.addUserData(data);
await viewModel.navigateToAssessment();
}
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
@ -30,6 +41,13 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: true,
onPop: viewModel.pop,
showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildExpandedBody(OnboardingViewModel viewModel) => Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
@ -77,15 +95,6 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
]; ];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: true,
onPop: viewModel.pop,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
Widget _buildTitle() => const Text( Widget _buildTitle() => const Text(
'Which topics interest you most?', 'Which topics interest you most?',
style: TextStyle( style: TextStyle(
@ -159,14 +168,14 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.selectedTopic != null onTap: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other' ? viewModel.selectedTopic?.toLowerCase() == 'other'
? viewModel.focusTopic ? topicController.text.isNotEmpty
? () => viewModel.next() ? () async => await _next(viewModel)
: null : null
: () => viewModel.next() : () async => await _next(viewModel)
: null, : null,
backgroundColor: viewModel.selectedTopic != null backgroundColor: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other' ? viewModel.selectedTopic?.toLowerCase() == 'other'
? viewModel.focusTopic && topicController.text.isNotEmpty ? topicController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1) : kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor : kcPrimaryColor

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/widgets/birthday_selector.dart'; import 'package:yimaru_app/ui/widgets/birthday_selector.dart';
@ -323,7 +324,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
List<Widget> _buildBirthdayChildren(ProfileDetailViewModel viewModel) => [ List<Widget> _buildBirthdayChildren(ProfileDetailViewModel viewModel) => [
_buildBirthdayLabel(), _buildBirthdayLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildBirthdayFormField(), _buildBirthdayFormField(viewModel),
]; ];
Widget _buildBirthdayLabel() => CustomFormLabel( Widget _buildBirthdayLabel() => CustomFormLabel(
@ -331,7 +332,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
style: style16DG600, style: style16DG600,
); );
Widget _buildBirthdayFormField() => const BirthdaySelector(); Widget _buildBirthdayFormField(ProfileDetailViewModel viewModel) =>
BirthdaySelector(
birthday: viewModel.selectedBirthday,
onSelected: (value) =>
viewModel.setBirthday(DateFormat('d MMM, yyyy').format(value)),
);
Widget _buildPhoneNumberFormFieldSection(ProfileDetailViewModel viewModel) => Widget _buildPhoneNumberFormFieldSection(ProfileDetailViewModel viewModel) =>
Column( Column(

View File

@ -7,7 +7,6 @@ import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/home/home_view.dart'; import 'package:yimaru_app/ui/views/home/home_view.dart';
import 'package:yimaru_app/ui/views/login/login_view.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/user_model.dart'; import '../../../models/user_model.dart';
@ -234,18 +233,20 @@ class RegisterViewModel extends FormViewModel {
Future<Map<String, dynamic>> _verifyOtp() async { Future<Map<String, dynamic>> _verifyOtp() async {
Map<String, dynamic> response = await _apiService.verifyOtp(_userData); Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
if (response['status'] == ResponseStatus.success) { if (response['status'] == ResponseStatus.success) {
// UserModel user = response['data'] as UserModel; UserModel user = response['data'] as UserModel;
// Map<String, dynamic> data = { Map<String, dynamic> data = {
// 'userId': user.userId, 'userId': user.userId,
// 'accessToken': user.accessToken, 'accessToken': user.accessToken,
// 'refreshToken': user.refreshToken 'refreshToken': user.refreshToken
// }; };
await _authenticationService.saveUserData({ // {
'userId': 10, // 'userId': 10,
'accessToken': 'accessToken', // 'accessToken': 'accessToken',
'refreshToken': 'refreshToken' // 'refreshToken': 'refreshToken'
}); // }
await _authenticationService.saveUserData(data);
showSuccessToast(response['message']); showSuccessToast(response['message']);
} else { } else {
showErrorToast(response['message']); showErrorToast(response['message']);

View File

@ -227,10 +227,8 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Sign Up', text: 'Sign Up',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: onTap: passwordController.text.isNotEmpty &&
(viewModel.focusPassword && passwordController.text.isNotEmpty) && confirmPasswordController.text.isNotEmpty &&
(viewModel.focusConfirmPassword &&
confirmPasswordController.text.isNotEmpty) &&
viewModel.number && viewModel.number &&
viewModel.length && viewModel.length &&
viewModel.specialChar && viewModel.specialChar &&
@ -239,10 +237,8 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
viewModel.agree viewModel.agree
? () async => await _signUp(viewModel) ? () async => await _signUp(viewModel)
: null, : null,
backgroundColor: backgroundColor: passwordController.text.isNotEmpty &&
(viewModel.focusPassword && passwordController.text.isNotEmpty) && confirmPasswordController.text.isNotEmpty &&
(viewModel.focusConfirmPassword &&
confirmPasswordController.text.isNotEmpty) &&
viewModel.number && viewModel.number &&
viewModel.length && viewModel.length &&
viewModel.specialChar && viewModel.specialChar &&

View File

@ -119,13 +119,11 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: viewModel.focusEmail && onTap: emailController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
!viewModel.hasEmailValidationMessage !viewModel.hasEmailValidationMessage
? () => _addUserData(viewModel) ? () => _addUserData(viewModel)
: null, : null,
backgroundColor: viewModel.focusEmail && backgroundColor: emailController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
!viewModel.hasEmailValidationMessage !viewModel.hasEmailValidationMessage
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),

View File

@ -138,12 +138,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: onTap: phoneNumberController.text.isNotEmpty
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
? () => viewModel.goTo(page: 3, type: RegistrationType.phone) ? () => viewModel.goTo(page: 3, type: RegistrationType.phone)
: null, : null,
backgroundColor: backgroundColor: phoneNumberController.text.isNotEmpty
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
); );

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_timer_countdown/flutter_timer_countdown.dart'; import 'package:flutter_timer_countdown/flutter_timer_countdown.dart';
import 'package:pinput/pinput.dart'; import 'package:pinput/pinput.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/register/register_viewmodel.dart'; import 'package:yimaru_app/ui/views/register/register_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_cursor.dart'; import 'package:yimaru_app/ui/widgets/custom_cursor.dart';
@ -164,14 +163,12 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: viewModel.focusOtp && backgroundColor:
otpController.text.length == 6 && otpController.text.length == 6 && !viewModel.hasOtpValidationMessage
!viewModel.hasOtpValidationMessage
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.focusOtp && onTap:
otpController.text.length == 6 && otpController.text.length == 6 && !viewModel.hasOtpValidationMessage
!viewModel.hasOtpValidationMessage
? () async => await _verifyOtp(viewModel) ? () async => await _verifyOtp(viewModel)
: null, : null,
); );

View File

@ -9,7 +9,8 @@ import '../../common/app_colors.dart';
import 'startup_viewmodel.dart'; import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> { class StartupView extends StackedView<StartupViewModel> {
const StartupView({Key? key}) : super(key: key); final String label;
const StartupView({Key? key, this.label = 'Loading'}) : super(key: key);
@override @override
Widget builder( Widget builder(
@ -70,7 +71,7 @@ class StartupView extends StackedView<StartupViewModel> {
]; ];
Widget _buildLoadingText() => Widget _buildLoadingText() =>
const Text('Loading ...', style: TextStyle(color: kcWhite, fontSize: 16)); Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16));
Widget _buildIndicatorWrapper() => SizedBox( Widget _buildIndicatorWrapper() => SizedBox(
width: 16, width: 16,

View File

@ -11,11 +11,17 @@ class StartupViewModel extends BaseViewModel {
// Place anything here that needs to happen before we get into the application // Place anything here that needs to happen before we get into the application
Future runStartupLogic() async { Future runStartupLogic() async {
final response = await _authenticationService.userLoggedIn(); final loggedIn = await _authenticationService.userLoggedIn();
if (response) { final firstTimeInstall = await _authenticationService.isFirstTimeInstall();
if (firstTimeInstall) {
_navigationService.replaceWithWelcomeView();
} else {
if (loggedIn) {
_navigationService.replaceWithHomeView(); _navigationService.replaceWithHomeView();
} else { } else {
_navigationService.replaceWithLoginView(); _navigationService.replaceWithLoginView();
} }
} }
} }
}

View File

@ -4,25 +4,26 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
class FirstWelcomeScreen extends ViewModelWidget<OnboardingViewModel> { import '../welcome_viewmodel.dart';
class FirstWelcomeScreen extends ViewModelWidget<WelcomeViewModel> {
const FirstWelcomeScreen({super.key}); const FirstWelcomeScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(), _buildBackground(),
_buildColumnWrapper(), _buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
@ -68,17 +69,17 @@ class FirstWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
), ),
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel), child: _buildButtonContainer(viewModel),
); );
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(WelcomeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,

View File

@ -4,25 +4,26 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
class SecondWelcomeScreen extends ViewModelWidget<OnboardingViewModel> { import '../welcome_viewmodel.dart';
class SecondWelcomeScreen extends ViewModelWidget<WelcomeViewModel> {
const SecondWelcomeScreen({super.key}); const SecondWelcomeScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(), _buildBackground(),
_buildColumnWrapper(), _buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
@ -68,17 +69,17 @@ class SecondWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
), ),
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel), child: _buildButtonContainer(viewModel),
); );
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButton(WelcomeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,

View File

@ -4,25 +4,27 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/views/onboarding/onboarding_viewmodel.dart';
class ThirdWelcomeScreen extends ViewModelWidget<OnboardingViewModel> { import '../../../widgets/custom_circular_progress_indicator.dart';
import '../welcome_viewmodel.dart';
class ThirdWelcomeScreen extends ViewModelWidget<WelcomeViewModel> {
const ThirdWelcomeScreen({super.key}); const ThirdWelcomeScreen({super.key});
@override @override
Widget build(BuildContext context, OnboardingViewModel viewModel) => Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack( Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(), _buildBackground(),
_buildColumnWrapper(), _buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
@ -52,6 +54,7 @@ class ThirdWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
]; ];
Widget _buildIcon() => SvgPicture.asset( Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg', 'assets/icons/logo.svg',
height: 50, height: 50,
@ -67,24 +70,30 @@ class ThirdWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
), ),
); );
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align( Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel), child: _buildButtonContainer(viewModel),
); );
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding( Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50), padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButtonState(viewModel),
); );
Widget _buildContinueButton(OnboardingViewModel viewModel) => Widget _buildContinueButtonState(WelcomeViewModel viewModel) =>
viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildContinueButton(WelcomeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Start Learning', text: 'Start Learning',
backgroundColor: kcWhite, backgroundColor: kcWhite,
trailingIcon: Icons.arrow_forward,
onTap: () => viewModel.next(),
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
trailingIcon: Icons.arrow_forward,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'screens/first_welcome_screen.dart';
import 'screens/second_welcome_screen.dart';
import 'screens/third_welcome_screen.dart';
import 'welcome_viewmodel.dart';
class WelcomeView extends StackedView<WelcomeViewModel> {
const WelcomeView({Key? key}) : super(key: key);
@override
WelcomeViewModel viewModelBuilder(BuildContext context) => WelcomeViewModel();
@override
Widget builder(
BuildContext context,
WelcomeViewModel viewModel,
Widget? child,
) =>
_buildWelcomeScreens(viewModel);
Widget _buildWelcomeScreens(WelcomeViewModel viewModel) => IndexedStack(
index: viewModel.currentPage,
children: _buildScreens(),
);
List<Widget> _buildScreens() =>
[_buildFirstWelcome(), _buildSecondWelcome(), _buildThirdWelcome()];
Widget _buildFirstWelcome() => const FirstWelcomeScreen();
Widget _buildSecondWelcome() => const SecondWelcomeScreen();
Widget _buildThirdWelcome() => const ThirdWelcomeScreen();
}

View File

@ -0,0 +1,35 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/services/authentication_service.dart';
import '../../../app/app.locator.dart';
class WelcomeViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
int _currentPage = 0;
int get currentPage => _currentPage;
Future<void> setFirstTimeInstall() async {
await runBusyFuture(_setFirstTimeInstall());
}
// First time install
Future<void> _setFirstTimeInstall() async {
await _authenticationService.setFirstTimeInstall(false);
await navigateToLogin();
}
// Navigation
Future<void> navigateToLogin() async =>
await _navigationService.navigateToLoginView();
void next() {
_currentPage++;
rebuildUi();
}
}

View File

@ -1,17 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/profile_detail/profile_detail_viewmodel.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
import 'package:omni_datetime_picker/omni_datetime_picker.dart'; import 'package:omni_datetime_picker/omni_datetime_picker.dart';
class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> { class BirthdaySelector extends StatelessWidget {
const BirthdaySelector({super.key}); final String? birthday;
final void Function(DateTime)? onSelected;
DateTime _initialDate(ProfileDetailViewModel viewModel) { const BirthdaySelector({super.key, this.birthday, this.onSelected});
DateTime _initialDate() {
try { try {
final parsedDate = format.parse(viewModel.selectedBirthday ?? ''); final parsedDate = format.parse(birthday ?? '');
return parsedDate.isAfter(DateTime.now()) ? DateTime.now() : parsedDate; return parsedDate.isAfter(DateTime.now()) ? DateTime.now() : parsedDate;
} catch (_) { } catch (_) {
return DateTime.now(); return DateTime.now();
@ -19,8 +20,8 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
} }
Future<void> _pickDateTime( Future<void> _pickDateTime(
{required BuildContext context, BuildContext context,
required ProfileDetailViewModel viewModel}) async { ) async {
DateTime? dateTime = await showOmniDateTimePicker( DateTime? dateTime = await showOmniDateTimePicker(
context: context, context: context,
is24HourMode: false, is24HourMode: false,
@ -28,10 +29,10 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
lastDate: DateTime.now(), lastDate: DateTime.now(),
firstDate: DateTime(1900), firstDate: DateTime(1900),
barrierDismissible: true, barrierDismissible: true,
initialDate: _initialDate(),
titleSeparator: const Divider(), titleSeparator: const Divider(),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
type: OmniDateTimePickerType.date, type: OmniDateTimePickerType.date,
initialDate: _initialDate(viewModel),
borderRadius: const BorderRadius.all(Radius.circular(15)), borderRadius: const BorderRadius.all(Radius.circular(15)),
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24), insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
title: const Text('Birthday', style: TextStyle(fontSize: 16)), title: const Text('Birthday', style: TextStyle(fontSize: 16)),
@ -42,56 +43,55 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
); );
if (dateTime != null) { if (dateTime != null) {
String formattedDateTime = DateFormat('d MMM, yyyy').format(dateTime); // String formattedDateTime = DateFormat('d MMM, yyyy').format(dateTime);
viewModel.setBirthday(DateFormat('d MMM, yyyy').format(dateTime)); if (onSelected != null) {
//onChanged(formattedDateTime); onSelected!(dateTime);
}
} }
} }
@override @override
Widget build(BuildContext context, ProfileDetailViewModel viewModel) => Widget build(
_buildButtonWrapper(context: context, viewModel: viewModel); BuildContext context,
) =>
_buildButtonWrapper(
context,
);
Widget _buildButtonWrapper( Widget _buildButtonWrapper(BuildContext context) => Container(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>
Container(
height: 50, height: 50,
width: double.maxFinite, width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: _buildContainerWrapper(context: context, viewModel: viewModel), child: _buildContainerWrapper(context),
); );
Widget _buildContainerWrapper( Widget _buildContainerWrapper(BuildContext context) => GestureDetector(
{required BuildContext context, onTap: () async => await _pickDateTime(
required ProfileDetailViewModel viewModel}) => context,
GestureDetector( ),
onTap: () async => child: _buildContainer(),
await _pickDateTime(context: context, viewModel: viewModel),
child: _buildContainer(viewModel),
); );
Widget _buildContainer(ProfileDetailViewModel viewModel) => Container( Widget _buildContainer() => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
color: kcPrimaryColor.withOpacity(0.1), color: kcPrimaryColor.withOpacity(0.1),
border: Border.all(color: kcPrimaryColor), border: Border.all(color: kcPrimaryColor),
), ),
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildButtonRowWrapper(viewModel), child: _buildButtonRowWrapper(),
); );
Widget _buildButtonRowWrapper(ProfileDetailViewModel viewModel) => Row( Widget _buildButtonRowWrapper() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildButtonRowChildren(viewModel), children: _buildButtonRowChildren(),
); );
List<Widget> _buildButtonRowChildren(ProfileDetailViewModel viewModel) => List<Widget> _buildButtonRowChildren() => [_buildText(), _buildIcon()];
[_buildText(viewModel), _buildIcon()];
Widget _buildText(ProfileDetailViewModel viewModel) => Text( Widget _buildText() => Text(
viewModel.selectedBirthday ?? 'Pick birthday', birthday ?? 'Pick birthday',
style: const TextStyle(color: kcDarkGrey), style: const TextStyle(color: kcDarkGrey),
); );

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../common/app_colors.dart';
class CustomCircularProgressIndicator extends StatelessWidget { class CustomCircularProgressIndicator extends StatelessWidget {
final Color color; final Color color;
const CustomCircularProgressIndicator({super.key, required this.color}); const CustomCircularProgressIndicator({super.key, required this.color});

View File

@ -42,10 +42,15 @@ class CustomSmallRadioButton extends StatelessWidget {
); );
List<Widget> _buildButtonRowChildren() => List<Widget> _buildButtonRowChildren() =>
[_buildText(), if (selected) _buildIcon()]; [_buildTextWrapper(), if (selected) _buildIcon()];
Widget _buildTextWrapper() => Expanded(
child: _buildText(),
);
Widget _buildText() => Text( Widget _buildText() => Text(
title, title,
maxLines: 2,
style: const TextStyle(color: kcDarkGrey), style: const TextStyle(color: kcDarkGrey),
); );

View File

@ -4,14 +4,12 @@ import 'package:yimaru_app/ui/widgets/language_button.dart';
class LargeAppBar extends StatelessWidget { class LargeAppBar extends StatelessWidget {
final bool showBackButton; final bool showBackButton;
final GestureTapCallback? onTap;
final GestureTapCallback? onPop; final GestureTapCallback? onPop;
final bool showLanguageSelection; final bool showLanguageSelection;
final GestureTapCallback? onLanguage; final GestureTapCallback? onLanguage;
const LargeAppBar( const LargeAppBar(
{super.key, {super.key,
this.onTap,
this.onPop, this.onPop,
this.onLanguage, this.onLanguage,
required this.showBackButton, required this.showBackButton,
@ -48,16 +46,16 @@ class LargeAppBar extends StatelessWidget {
); );
Widget _buildBackButton() => BackButton( Widget _buildBackButton() => BackButton(
onPressed: onTap, onPressed: onPop,
style: style:
const ButtonStyle(foregroundColor: WidgetStatePropertyAll(kcWhite)), const ButtonStyle(foregroundColor: WidgetStatePropertyAll(kcWhite)),
); );
Widget _buildRightButton() => Align( Widget _buildRightButton() => Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
child: showLanguageSelection child: showLanguageSelection ? _buildLanguageSelector() : Container()
? _buildLanguageSelector() // _buildCloseButton()
: _buildCloseButton()); );
Widget _buildLanguageSelector() => LanguageButton( Widget _buildLanguageSelector() => LanguageButton(
language: 'EN', language: 'EN',

View File

@ -5,10 +5,14 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import battery_plus
import connectivity_plus
import flutter_secure_storage_darwin import flutter_secure_storage_darwin
import path_provider_foundation import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
} }

View File

@ -33,6 +33,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.0"
battery_plus:
dependency: "direct main"
description:
name: battery_plus
sha256: ad16fcb55b7384be6b4bbc763d5e2031ac7ea62b2d9b6b661490c7b9741155bf
url: "https://pub.dev"
source: hosted
version: "7.0.0"
battery_plus_platform_interface:
dependency: transitive
description:
name: battery_plus_platform_interface
sha256: e8342c0f32de4b1dfd0223114b6785e48e579bfc398da9471c9179b907fa4910
url: "https://pub.dev"
source: hosted
version: "2.0.1"
bloc: bloc:
dependency: transitive dependency: transitive
description: description:
@ -153,6 +169,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.19.1" version: "1.19.1"
connectivity_plus:
dependency: transitive
description:
name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -185,6 +217,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.6" version: "2.3.6"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
@ -464,6 +504,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
in_app_update:
dependency: "direct main"
description:
name: in_app_update
sha256: "9924a3efe592e1c0ec89dda3683b3cfec3d4cd02d908e6de00c24b759038ddb1"
url: "https://pub.dev"
source: hosted
version: "4.2.5"
internet_connection_checker_plus:
dependency: "direct main"
description:
name: internet_connection_checker_plus
sha256: ef43530f24de6309f99802358f8a543ea1f2babc153effc84a75133751716892
url: "https://pub.dev"
source: hosted
version: "2.9.1+2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -608,6 +664,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
omni_datetime_picker: omni_datetime_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -861,6 +925,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.2" version: "1.4.2"
storage_info:
dependency: "direct main"
description:
name: storage_info
sha256: adbf5fd1a7c2ca977dd828573820db0a0a16f4aa317e0ab72e9b3282eb5bbe42
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -933,6 +1005,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
upower:
dependency: transitive
description:
name: upower
sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf
url: "https://pub.dev"
source: hosted
version: "0.7.0"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:

View File

@ -16,8 +16,11 @@ dependencies:
iconsax: ^0.0.8 iconsax: ^0.0.8
flutter_svg: ^2.2.3 flutter_svg: ^2.2.3
stacked_shared: any stacked_shared: any
battery_plus: ^7.0.0
storage_info: ^1.0.0
flutter_html: ^3.0.0 flutter_html: ^3.0.0
email_validator: any email_validator: any
in_app_update: ^4.2.5
toastification: ^3.0.3 toastification: ^3.0.3
dropdown_search: ^6.0.2 dropdown_search: ^6.0.2
json_annotation: ^4.9.0 json_annotation: ^4.9.0
@ -26,6 +29,7 @@ dependencies:
json_serializable: ^6.8.0 json_serializable: ^6.8.0
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
flutter_timer_countdown: ^1.0.7 flutter_timer_countdown: ^1.0.7
internet_connection_checker_plus: ^2.9.1+2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,6 +6,7 @@ import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/api_service.dart'; import 'package:yimaru_app/services/api_service.dart';
import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart';
import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
// @stacked-import // @stacked-import
import 'test_helpers.mocks.dart'; import 'test_helpers.mocks.dart';
@ -20,6 +21,7 @@ import 'test_helpers.mocks.dart';
MockSpec<ApiService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<ApiService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<SecureStorageService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<SecureStorageService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<DioService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<DioService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<StatusCheckerService>(onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec // @stacked-mock-spec
], ],
) )
@ -31,6 +33,7 @@ void registerServices() {
getAndRegisterApiService(); getAndRegisterApiService();
getAndRegisterSecureStorageService(); getAndRegisterSecureStorageService();
getAndRegisterDioService(); getAndRegisterDioService();
getAndRegisterStatusCheckerService();
// @stacked-mock-register // @stacked-mock-register
} }
@ -115,6 +118,13 @@ MockDioService getAndRegisterDioService() {
locator.registerSingleton<DioService>(service); locator.registerSingleton<DioService>(service);
return service; return service;
} }
MockStatusCheckerService getAndRegisterStatusCheckerService() {
_removeRegistrationIfExists<StatusCheckerService>();
final service = MockStatusCheckerService();
locator.registerSingleton<StatusCheckerService>(service);
return service;
}
// @stacked-mock-create // @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() { void _removeRegistrationIfExists<T extends Object>() {

View File

@ -3,18 +3,21 @@
// Do not manually edit this file. // Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i6; import 'dart:async' as _i8;
import 'dart:ui' as _i7; import 'dart:ui' as _i9;
import 'package:flutter/material.dart' as _i5; import 'package:dio/dio.dart' as _i3;
import 'package:flutter/material.dart' as _i7;
import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i4; import 'package:mockito/src/dummies.dart' as _i6;
import 'package:stacked_services/stacked_services.dart' as _i3; import 'package:stacked_services/stacked_services.dart' as _i5;
import 'package:yimaru_app/models/assessment.dart' as _i12;
import 'package:yimaru_app/models/user_model.dart' as _i2; import 'package:yimaru_app/models/user_model.dart' as _i2;
import 'package:yimaru_app/services/api_service.dart' as _i9; import 'package:yimaru_app/services/api_service.dart' as _i11;
import 'package:yimaru_app/services/authentication_service.dart' as _i8; import 'package:yimaru_app/services/authentication_service.dart' as _i10;
import 'package:yimaru_app/services/dio_service.dart' as _i11; import 'package:yimaru_app/services/dio_service.dart' as _i13;
import 'package:yimaru_app/services/secure_storage_service.dart' as _i10; import 'package:yimaru_app/services/secure_storage_service.dart' as _i4;
import 'package:yimaru_app/services/status_checker_service.dart' as _i14;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_redundant_argument_values
@ -39,18 +42,39 @@ class _FakeUserModel_0 extends _i1.SmartFake implements _i2.UserModel {
); );
} }
class _FakeDio_1 extends _i1.SmartFake implements _i3.Dio {
_FakeDio_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeSecureStorageService_2 extends _i1.SmartFake
implements _i4.SecureStorageService {
_FakeSecureStorageService_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [NavigationService]. /// A class which mocks [NavigationService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockNavigationService extends _i1.Mock implements _i3.NavigationService { class MockNavigationService extends _i1.Mock implements _i5.NavigationService {
@override @override
String get previousRoute => (super.noSuchMethod( String get previousRoute => (super.noSuchMethod(
Invocation.getter(#previousRoute), Invocation.getter(#previousRoute),
returnValue: _i4.dummyValue<String>( returnValue: _i6.dummyValue<String>(
this, this,
Invocation.getter(#previousRoute), Invocation.getter(#previousRoute),
), ),
returnValueForMissingStub: _i4.dummyValue<String>( returnValueForMissingStub: _i6.dummyValue<String>(
this, this,
Invocation.getter(#previousRoute), Invocation.getter(#previousRoute),
), ),
@ -59,25 +83,25 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
@override @override
String get currentRoute => (super.noSuchMethod( String get currentRoute => (super.noSuchMethod(
Invocation.getter(#currentRoute), Invocation.getter(#currentRoute),
returnValue: _i4.dummyValue<String>( returnValue: _i6.dummyValue<String>(
this, this,
Invocation.getter(#currentRoute), Invocation.getter(#currentRoute),
), ),
returnValueForMissingStub: _i4.dummyValue<String>( returnValueForMissingStub: _i6.dummyValue<String>(
this, this,
Invocation.getter(#currentRoute), Invocation.getter(#currentRoute),
), ),
) as String); ) as String);
@override @override
_i5.GlobalKey<_i5.NavigatorState>? nestedNavigationKey(int? index) => _i7.GlobalKey<_i7.NavigatorState>? nestedNavigationKey(int? index) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#nestedNavigationKey, #nestedNavigationKey,
[index], [index],
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i5.GlobalKey<_i5.NavigatorState>?); ) as _i7.GlobalKey<_i7.NavigatorState>?);
@override @override
void config({ void config({
@ -86,7 +110,7 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
bool? defaultOpaqueRoute, bool? defaultOpaqueRoute,
Duration? defaultDurationTransition, Duration? defaultDurationTransition,
bool? defaultGlobalState, bool? defaultGlobalState,
_i3.Transition? defaultTransitionStyle, _i5.Transition? defaultTransitionStyle,
String? defaultTransition, String? defaultTransition,
}) => }) =>
super.noSuchMethod( super.noSuchMethod(
@ -107,18 +131,18 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
); );
@override @override
_i6.Future<T?>? navigateWithTransition<T>( _i8.Future<T?>? navigateWithTransition<T>(
_i5.Widget? page, { _i7.Widget? page, {
bool? opaque, bool? opaque,
String? transition = r'', String? transition = r'',
Duration? duration, Duration? duration,
bool? popGesture, bool? popGesture,
int? id, int? id,
_i5.Curve? curve, _i7.Curve? curve,
bool? fullscreenDialog = false, bool? fullscreenDialog = false,
bool? preventDuplicates = true, bool? preventDuplicates = true,
_i3.Transition? transitionClass, _i5.Transition? transitionClass,
_i3.Transition? transitionStyle, _i5.Transition? transitionStyle,
String? routeName, String? routeName,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
@ -140,21 +164,21 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? replaceWithTransition<T>( _i8.Future<T?>? replaceWithTransition<T>(
_i5.Widget? page, { _i7.Widget? page, {
bool? opaque, bool? opaque,
String? transition = r'', String? transition = r'',
Duration? duration, Duration? duration,
bool? popGesture, bool? popGesture,
int? id, int? id,
_i5.Curve? curve, _i7.Curve? curve,
bool? fullscreenDialog = false, bool? fullscreenDialog = false,
bool? preventDuplicates = true, bool? preventDuplicates = true,
_i3.Transition? transitionClass, _i5.Transition? transitionClass,
_i3.Transition? transitionStyle, _i5.Transition? transitionStyle,
String? routeName, String? routeName,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
@ -176,7 +200,7 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
bool back<T>({ bool back<T>({
@ -198,7 +222,7 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
@override @override
void popUntil( void popUntil(
_i5.RoutePredicate? predicate, { _i7.RoutePredicate? predicate, {
int? id, int? id,
}) => }) =>
super.noSuchMethod( super.noSuchMethod(
@ -220,13 +244,13 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
); );
@override @override
_i6.Future<T?>? navigateTo<T>( _i8.Future<T?>? navigateTo<T>(
String? routeName, { String? routeName, {
dynamic arguments, dynamic arguments,
int? id, int? id,
bool? preventDuplicates = true, bool? preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
_i5.RouteTransitionsBuilder? transition, _i7.RouteTransitionsBuilder? transition,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -241,21 +265,21 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? navigateToView<T>( _i8.Future<T?>? navigateToView<T>(
_i5.Widget? view, { _i7.Widget? view, {
dynamic arguments, dynamic arguments,
int? id, int? id,
bool? opaque, bool? opaque,
_i5.Curve? curve, _i7.Curve? curve,
Duration? duration, Duration? duration,
bool? fullscreenDialog = false, bool? fullscreenDialog = false,
bool? popGesture, bool? popGesture,
bool? preventDuplicates = true, bool? preventDuplicates = true,
_i3.Transition? transition, _i5.Transition? transition,
_i3.Transition? transitionStyle, _i5.Transition? transitionStyle,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -275,16 +299,16 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? replaceWith<T>( _i8.Future<T?>? replaceWith<T>(
String? routeName, { String? routeName, {
dynamic arguments, dynamic arguments,
int? id, int? id,
bool? preventDuplicates = true, bool? preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
_i5.RouteTransitionsBuilder? transition, _i7.RouteTransitionsBuilder? transition,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -299,10 +323,10 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? clearStackAndShow<T>( _i8.Future<T?>? clearStackAndShow<T>(
String? routeName, { String? routeName, {
dynamic arguments, dynamic arguments,
int? id, int? id,
@ -319,11 +343,11 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? clearStackAndShowView<T>( _i8.Future<T?>? clearStackAndShowView<T>(
_i5.Widget? view, { _i7.Widget? view, {
dynamic arguments, dynamic arguments,
int? id, int? id,
}) => }) =>
@ -337,10 +361,10 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? clearTillFirstAndShow<T>( _i8.Future<T?>? clearTillFirstAndShow<T>(
String? routeName, { String? routeName, {
dynamic arguments, dynamic arguments,
int? id, int? id,
@ -359,11 +383,11 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? clearTillFirstAndShowView<T>( _i8.Future<T?>? clearTillFirstAndShowView<T>(
_i5.Widget? view, { _i7.Widget? view, {
dynamic arguments, dynamic arguments,
int? id, int? id,
}) => }) =>
@ -377,12 +401,12 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
@override @override
_i6.Future<T?>? pushNamedAndRemoveUntil<T>( _i8.Future<T?>? pushNamedAndRemoveUntil<T>(
String? routeName, { String? routeName, {
_i5.RoutePredicate? predicate, _i7.RoutePredicate? predicate,
dynamic arguments, dynamic arguments,
int? id, int? id,
}) => }) =>
@ -397,16 +421,16 @@ class MockNavigationService extends _i1.Mock implements _i3.NavigationService {
}, },
), ),
returnValueForMissingStub: null, returnValueForMissingStub: null,
) as _i6.Future<T?>?); ) as _i8.Future<T?>?);
} }
/// A class which mocks [BottomSheetService]. /// A class which mocks [BottomSheetService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockBottomSheetService extends _i1.Mock class MockBottomSheetService extends _i1.Mock
implements _i3.BottomSheetService { implements _i5.BottomSheetService {
@override @override
void setCustomSheetBuilders(Map<dynamic, _i3.SheetBuilder>? builders) => void setCustomSheetBuilders(Map<dynamic, _i5.SheetBuilder>? builders) =>
super.noSuchMethod( super.noSuchMethod(
Invocation.method( Invocation.method(
#setCustomSheetBuilders, #setCustomSheetBuilders,
@ -416,7 +440,7 @@ class MockBottomSheetService extends _i1.Mock
); );
@override @override
_i6.Future<_i3.SheetResponse<dynamic>?> showBottomSheet({ _i8.Future<_i5.SheetResponse<dynamic>?> showBottomSheet({
required String? title, required String? title,
String? description, String? description,
String? confirmButtonTitle = r'Ok', String? confirmButtonTitle = r'Ok',
@ -449,13 +473,13 @@ class MockBottomSheetService extends _i1.Mock
#elevation: elevation, #elevation: elevation,
}, },
), ),
returnValue: _i6.Future<_i3.SheetResponse<dynamic>?>.value(), returnValue: _i8.Future<_i5.SheetResponse<dynamic>?>.value(),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<_i3.SheetResponse<dynamic>?>.value(), _i8.Future<_i5.SheetResponse<dynamic>?>.value(),
) as _i6.Future<_i3.SheetResponse<dynamic>?>); ) as _i8.Future<_i5.SheetResponse<dynamic>?>);
@override @override
_i6.Future<_i3.SheetResponse<T>?> showCustomSheet<T, R>({ _i8.Future<_i5.SheetResponse<T>?> showCustomSheet<T, R>({
dynamic variant, dynamic variant,
String? title, String? title,
String? description, String? description,
@ -468,7 +492,7 @@ class MockBottomSheetService extends _i1.Mock
bool? showIconInAdditionalButton = false, bool? showIconInAdditionalButton = false,
String? additionalButtonTitle, String? additionalButtonTitle,
bool? takesInput = false, bool? takesInput = false,
_i7.Color? barrierColor = const _i7.Color(2315255808), _i9.Color? barrierColor = const _i9.Color(2315255808),
double? elevation = 1.0, double? elevation = 1.0,
bool? barrierDismissible = true, bool? barrierDismissible = true,
bool? isScrollControlled = false, bool? isScrollControlled = false,
@ -512,12 +536,12 @@ class MockBottomSheetService extends _i1.Mock
#useRootNavigator: useRootNavigator, #useRootNavigator: useRootNavigator,
}, },
), ),
returnValue: _i6.Future<_i3.SheetResponse<T>?>.value(), returnValue: _i8.Future<_i5.SheetResponse<T>?>.value(),
returnValueForMissingStub: _i6.Future<_i3.SheetResponse<T>?>.value(), returnValueForMissingStub: _i8.Future<_i5.SheetResponse<T>?>.value(),
) as _i6.Future<_i3.SheetResponse<T>?>); ) as _i8.Future<_i5.SheetResponse<T>?>);
@override @override
void completeSheet(_i3.SheetResponse<dynamic>? response) => void completeSheet(_i5.SheetResponse<dynamic>? response) =>
super.noSuchMethod( super.noSuchMethod(
Invocation.method( Invocation.method(
#completeSheet, #completeSheet,
@ -530,10 +554,10 @@ class MockBottomSheetService extends _i1.Mock
/// A class which mocks [DialogService]. /// A class which mocks [DialogService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockDialogService extends _i1.Mock implements _i3.DialogService { class MockDialogService extends _i1.Mock implements _i5.DialogService {
@override @override
void registerCustomDialogBuilders( void registerCustomDialogBuilders(
Map<dynamic, _i3.DialogBuilder>? builders) => Map<dynamic, _i5.DialogBuilder>? builders) =>
super.noSuchMethod( super.noSuchMethod(
Invocation.method( Invocation.method(
#registerCustomDialogBuilders, #registerCustomDialogBuilders,
@ -545,10 +569,10 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
@override @override
void registerCustomDialogBuilder({ void registerCustomDialogBuilder({
required dynamic variant, required dynamic variant,
required _i5.Widget Function( required _i7.Widget Function(
_i5.BuildContext, _i7.BuildContext,
_i3.DialogRequest<dynamic>, _i5.DialogRequest<dynamic>,
dynamic Function(_i3.DialogResponse<dynamic>), dynamic Function(_i5.DialogResponse<dynamic>),
)? builder, )? builder,
}) => }) =>
super.noSuchMethod( super.noSuchMethod(
@ -564,17 +588,17 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
); );
@override @override
_i6.Future<_i3.DialogResponse<dynamic>?> showDialog({ _i8.Future<_i5.DialogResponse<dynamic>?> showDialog({
String? title, String? title,
String? description, String? description,
String? cancelTitle, String? cancelTitle,
_i7.Color? cancelTitleColor, _i9.Color? cancelTitleColor,
String? buttonTitle = r'Ok', String? buttonTitle = r'Ok',
_i7.Color? buttonTitleColor, _i9.Color? buttonTitleColor,
bool? barrierDismissible = false, bool? barrierDismissible = false,
_i5.RouteSettings? routeSettings, _i7.RouteSettings? routeSettings,
_i5.GlobalKey<_i5.NavigatorState>? navigatorKey, _i7.GlobalKey<_i7.NavigatorState>? navigatorKey,
_i3.DialogPlatform? dialogPlatform, _i5.DialogPlatform? dialogPlatform,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -593,13 +617,13 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
#dialogPlatform: dialogPlatform, #dialogPlatform: dialogPlatform,
}, },
), ),
returnValue: _i6.Future<_i3.DialogResponse<dynamic>?>.value(), returnValue: _i8.Future<_i5.DialogResponse<dynamic>?>.value(),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<_i3.DialogResponse<dynamic>?>.value(), _i8.Future<_i5.DialogResponse<dynamic>?>.value(),
) as _i6.Future<_i3.DialogResponse<dynamic>?>); ) as _i8.Future<_i5.DialogResponse<dynamic>?>);
@override @override
_i6.Future<_i3.DialogResponse<T>?> showCustomDialog<T, R>({ _i8.Future<_i5.DialogResponse<T>?> showCustomDialog<T, R>({
dynamic variant, dynamic variant,
String? title, String? title,
String? description, String? description,
@ -612,13 +636,13 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
bool? showIconInAdditionalButton = false, bool? showIconInAdditionalButton = false,
String? additionalButtonTitle, String? additionalButtonTitle,
bool? takesInput = false, bool? takesInput = false,
_i7.Color? barrierColor = const _i7.Color(2315255808), _i9.Color? barrierColor = const _i9.Color(2315255808),
bool? barrierDismissible = false, bool? barrierDismissible = false,
String? barrierLabel = r'', String? barrierLabel = r'',
bool? useSafeArea = true, bool? useSafeArea = true,
_i5.RouteSettings? routeSettings, _i7.RouteSettings? routeSettings,
_i5.GlobalKey<_i5.NavigatorState>? navigatorKey, _i7.GlobalKey<_i7.NavigatorState>? navigatorKey,
_i5.RouteTransitionsBuilder? transitionBuilder, _i7.RouteTransitionsBuilder? transitionBuilder,
dynamic customData, dynamic customData,
R? data, R? data,
}) => }) =>
@ -650,21 +674,21 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
#data: data, #data: data,
}, },
), ),
returnValue: _i6.Future<_i3.DialogResponse<T>?>.value(), returnValue: _i8.Future<_i5.DialogResponse<T>?>.value(),
returnValueForMissingStub: _i6.Future<_i3.DialogResponse<T>?>.value(), returnValueForMissingStub: _i8.Future<_i5.DialogResponse<T>?>.value(),
) as _i6.Future<_i3.DialogResponse<T>?>); ) as _i8.Future<_i5.DialogResponse<T>?>);
@override @override
_i6.Future<_i3.DialogResponse<dynamic>?> showConfirmationDialog({ _i8.Future<_i5.DialogResponse<dynamic>?> showConfirmationDialog({
String? title, String? title,
String? description, String? description,
String? cancelTitle = r'Cancel', String? cancelTitle = r'Cancel',
_i7.Color? cancelTitleColor, _i9.Color? cancelTitleColor,
String? confirmationTitle = r'Ok', String? confirmationTitle = r'Ok',
_i7.Color? confirmationTitleColor, _i9.Color? confirmationTitleColor,
bool? barrierDismissible = false, bool? barrierDismissible = false,
_i5.RouteSettings? routeSettings, _i7.RouteSettings? routeSettings,
_i3.DialogPlatform? dialogPlatform, _i5.DialogPlatform? dialogPlatform,
}) => }) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -682,13 +706,13 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
#dialogPlatform: dialogPlatform, #dialogPlatform: dialogPlatform,
}, },
), ),
returnValue: _i6.Future<_i3.DialogResponse<dynamic>?>.value(), returnValue: _i8.Future<_i5.DialogResponse<dynamic>?>.value(),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<_i3.DialogResponse<dynamic>?>.value(), _i8.Future<_i5.DialogResponse<dynamic>?>.value(),
) as _i6.Future<_i3.DialogResponse<dynamic>?>); ) as _i8.Future<_i5.DialogResponse<dynamic>?>);
@override @override
void completeDialog(_i3.DialogResponse<dynamic>? response) => void completeDialog(_i5.DialogResponse<dynamic>? response) =>
super.noSuchMethod( super.noSuchMethod(
Invocation.method( Invocation.method(
#completeDialog, #completeDialog,
@ -702,35 +726,113 @@ class MockDialogService extends _i1.Mock implements _i3.DialogService {
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockAuthenticationService extends _i1.Mock class MockAuthenticationService extends _i1.Mock
implements _i8.AuthenticationService { implements _i10.AuthenticationService {
@override @override
_i6.Future<bool> userLoggedIn() => (super.noSuchMethod( _i8.Future<bool> userLoggedIn() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#userLoggedIn, #userLoggedIn,
[], [],
), ),
returnValue: _i6.Future<bool>.value(false), returnValue: _i8.Future<bool>.value(false),
returnValueForMissingStub: _i6.Future<bool>.value(false), returnValueForMissingStub: _i8.Future<bool>.value(false),
) as _i6.Future<bool>); ) as _i8.Future<bool>);
@override @override
_i6.Future<void> saveUserData(Map<String, dynamic>? data) => _i8.Future<String?> getAccessToken() => (super.noSuchMethod(
Invocation.method(
#getAccessToken,
[],
),
returnValue: _i8.Future<String?>.value(),
returnValueForMissingStub: _i8.Future<String?>.value(),
) as _i8.Future<String?>);
@override
_i8.Future<String?> getRefreshToken() => (super.noSuchMethod(
Invocation.method(
#getRefreshToken,
[],
),
returnValue: _i8.Future<String?>.value(),
returnValueForMissingStub: _i8.Future<String?>.value(),
) as _i8.Future<String?>);
@override
_i8.Future<int?> getUserId() => (super.noSuchMethod(
Invocation.method(
#getUserId,
[],
),
returnValue: _i8.Future<int?>.value(),
returnValueForMissingStub: _i8.Future<int?>.value(),
) as _i8.Future<int?>);
@override
_i8.Future<void> saveTokens({
required String? access,
required String? refresh,
}) =>
(super.noSuchMethod(
Invocation.method(
#saveTokens,
[],
{
#access: access,
#refresh: refresh,
},
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i8.Future<void> saveUserData(Map<String, dynamic>? data) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#saveUserData, #saveUserData,
[data], [data],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
@override @override
_i6.Future<_i2.UserModel> getUser() => (super.noSuchMethod( _i8.Future<void> saveProfileCompleted(bool? value) => (super.noSuchMethod(
Invocation.method(
#saveProfileCompleted,
[value],
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i8.Future<bool> isFirstTimeInstall() => (super.noSuchMethod(
Invocation.method(
#isFirstTimeInstall,
[],
),
returnValue: _i8.Future<bool>.value(false),
returnValueForMissingStub: _i8.Future<bool>.value(false),
) as _i8.Future<bool>);
@override
_i8.Future<void> setFirstTimeInstall(bool? value) => (super.noSuchMethod(
Invocation.method(
#setFirstTimeInstall,
[value],
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i8.Future<_i2.UserModel> getUser() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getUser, #getUser,
[], [],
), ),
returnValue: _i6.Future<_i2.UserModel>.value(_FakeUserModel_0( returnValue: _i8.Future<_i2.UserModel>.value(_FakeUserModel_0(
this, this,
Invocation.method( Invocation.method(
#getUser, #getUser,
@ -738,143 +840,175 @@ class MockAuthenticationService extends _i1.Mock
), ),
)), )),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<_i2.UserModel>.value(_FakeUserModel_0( _i8.Future<_i2.UserModel>.value(_FakeUserModel_0(
this, this,
Invocation.method( Invocation.method(
#getUser, #getUser,
[], [],
), ),
)), )),
) as _i6.Future<_i2.UserModel>); ) as _i8.Future<_i2.UserModel>);
@override @override
_i6.Future<void> logOut() => (super.noSuchMethod( _i8.Future<void> logOut() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#logOut, #logOut,
[], [],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
} }
/// A class which mocks [ApiService]. /// A class which mocks [ApiService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockApiService extends _i1.Mock implements _i9.ApiService { class MockApiService extends _i1.Mock implements _i11.ApiService {
@override @override
_i6.Future<Map<String, dynamic>> register(Map<String, dynamic>? data) => _i8.Future<Map<String, dynamic>> register(Map<String, dynamic>? data) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#register, #register,
[data], [data],
), ),
returnValue: returnValue:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>); ) as _i8.Future<Map<String, dynamic>>);
@override @override
_i6.Future<Map<String, dynamic>> login(Map<String, dynamic>? data) => _i8.Future<Map<String, dynamic>> login(Map<String, dynamic>? data) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#login, #login,
[data], [data],
), ),
returnValue: returnValue:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>); ) as _i8.Future<Map<String, dynamic>>);
@override @override
_i6.Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic>? data) => _i8.Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic>? data) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#verifyOtp, #verifyOtp,
[data], [data],
), ),
returnValue: returnValue:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>); ) as _i8.Future<Map<String, dynamic>>);
@override @override
_i6.Future<Map<String, dynamic>> resendOtp(Map<String, dynamic>? data) => _i8.Future<Map<String, dynamic>> resendOtp(Map<String, dynamic>? data) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#resendOtp, #resendOtp,
[data], [data],
), ),
returnValue: returnValue:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>); ) as _i8.Future<Map<String, dynamic>>);
@override @override
_i6.Future<Map<String, dynamic>> getProfileStatus(_i2.UserModel? user) => _i8.Future<Map<String, dynamic>> getProfileStatus(_i2.UserModel? user) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getProfileStatus, #getProfileStatus,
[user], [user],
), ),
returnValue: returnValue:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub: returnValueForMissingStub:
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i6.Future<Map<String, dynamic>>); ) as _i8.Future<Map<String, dynamic>>);
@override
_i8.Future<Map<String, dynamic>> updateProfile({
required _i2.UserModel? user,
required Map<String, dynamic>? data,
}) =>
(super.noSuchMethod(
Invocation.method(
#updateProfile,
[],
{
#user: user,
#data: data,
},
),
returnValue:
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
returnValueForMissingStub:
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i8.Future<Map<String, dynamic>>);
@override
_i8.Future<List<_i12.Assessment>> getAssessments() => (super.noSuchMethod(
Invocation.method(
#getAssessments,
[],
),
returnValue:
_i8.Future<List<_i12.Assessment>>.value(<_i12.Assessment>[]),
returnValueForMissingStub:
_i8.Future<List<_i12.Assessment>>.value(<_i12.Assessment>[]),
) as _i8.Future<List<_i12.Assessment>>);
} }
/// A class which mocks [SecureStorageService]. /// A class which mocks [SecureStorageService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockSecureStorageService extends _i1.Mock class MockSecureStorageService extends _i1.Mock
implements _i10.SecureStorageService { implements _i4.SecureStorageService {
@override @override
_i6.Future<void> clear() => (super.noSuchMethod( _i8.Future<void> clear() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#clear, #clear,
[], [],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
@override @override
_i6.Future<bool?> getBool(String? key) => (super.noSuchMethod( _i8.Future<bool?> getBool(String? key) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getBool, #getBool,
[key], [key],
), ),
returnValue: _i6.Future<bool?>.value(), returnValue: _i8.Future<bool?>.value(),
returnValueForMissingStub: _i6.Future<bool?>.value(), returnValueForMissingStub: _i8.Future<bool?>.value(),
) as _i6.Future<bool?>); ) as _i8.Future<bool?>);
@override @override
_i6.Future<String?> getString(String? key) => (super.noSuchMethod( _i8.Future<String?> getString(String? key) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getString, #getString,
[key], [key],
), ),
returnValue: _i6.Future<String?>.value(), returnValue: _i8.Future<String?>.value(),
returnValueForMissingStub: _i6.Future<String?>.value(), returnValueForMissingStub: _i8.Future<String?>.value(),
) as _i6.Future<String?>); ) as _i8.Future<String?>);
@override @override
_i6.Future<int?> getInt(String? key) => (super.noSuchMethod( _i8.Future<int?> getInt(String? key) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getInt, #getInt,
[key], [key],
), ),
returnValue: _i6.Future<int?>.value(), returnValue: _i8.Future<int?>.value(),
returnValueForMissingStub: _i6.Future<int?>.value(), returnValueForMissingStub: _i8.Future<int?>.value(),
) as _i6.Future<int?>); ) as _i8.Future<int?>);
@override @override
_i6.Future<void> setString( _i8.Future<void> setString(
String? key, String? key,
String? value, String? value,
) => ) =>
@ -886,12 +1020,12 @@ class MockSecureStorageService extends _i1.Mock
value, value,
], ],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
@override @override
_i6.Future<void> setInt( _i8.Future<void> setInt(
String? key, String? key,
int? value, int? value,
) => ) =>
@ -903,12 +1037,12 @@ class MockSecureStorageService extends _i1.Mock
value, value,
], ],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
@override @override
_i6.Future<void> setBool( _i8.Future<void> setBool(
String? key, String? key,
bool? value, bool? value,
) => ) =>
@ -920,12 +1054,101 @@ class MockSecureStorageService extends _i1.Mock
value, value,
], ],
), ),
returnValue: _i6.Future<void>.value(), returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i6.Future<void>.value(), returnValueForMissingStub: _i8.Future<void>.value(),
) as _i6.Future<void>); ) as _i8.Future<void>);
} }
/// A class which mocks [DioService]. /// A class which mocks [DioService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockDioService extends _i1.Mock implements _i11.DioService {} class MockDioService extends _i1.Mock implements _i13.DioService {
@override
_i3.Dio get dio => (super.noSuchMethod(
Invocation.getter(#dio),
returnValue: _FakeDio_1(
this,
Invocation.getter(#dio),
),
returnValueForMissingStub: _FakeDio_1(
this,
Invocation.getter(#dio),
),
) as _i3.Dio);
}
/// A class which mocks [StatusCheckerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockStatusCheckerService extends _i1.Mock
implements _i14.StatusCheckerService {
@override
_i4.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage),
returnValue: _FakeSecureStorageService_2(
this,
Invocation.getter(#storage),
),
returnValueForMissingStub: _FakeSecureStorageService_2(
this,
Invocation.getter(#storage),
),
) as _i4.SecureStorageService);
@override
bool get previousConnection => (super.noSuchMethod(
Invocation.getter(#previousConnection),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i8.Future<int> getBatteryLevel() => (super.noSuchMethod(
Invocation.method(
#getBatteryLevel,
[],
),
returnValue: _i8.Future<int>.value(0),
returnValueForMissingStub: _i8.Future<int>.value(0),
) as _i8.Future<int>);
@override
_i8.Future<bool> userAuthenticated() => (super.noSuchMethod(
Invocation.method(
#userAuthenticated,
[],
),
returnValue: _i8.Future<bool>.value(false),
returnValueForMissingStub: _i8.Future<bool>.value(false),
) as _i8.Future<bool>);
@override
_i8.Future<bool> checkConnection() => (super.noSuchMethod(
Invocation.method(
#checkConnection,
[],
),
returnValue: _i8.Future<bool>.value(false),
returnValueForMissingStub: _i8.Future<bool>.value(false),
) as _i8.Future<bool>);
@override
_i8.Future<int> getAvailableStorage() => (super.noSuchMethod(
Invocation.method(
#getAvailableStorage,
[],
),
returnValue: _i8.Future<int>.value(0),
returnValueForMissingStub: _i8.Future<int>.value(0),
) as _i8.Future<int>);
@override
_i8.Future<void> checkAndUpdate() => (super.noSuchMethod(
Invocation.method(
#checkAndUpdate,
[],
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
}

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('StatusCheckerServiceTest -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('AssessmentViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('WelcomeViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

@ -6,9 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <battery_plus/battery_plus_windows_plugin.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
BatteryPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
} }

View File

@ -3,6 +3,8 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
battery_plus
connectivity_plus
flutter_secure_storage_windows flutter_secure_storage_windows
) )