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
android:label="Yimaru"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
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/secure_storage_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
@StackedApp(
@ -50,6 +53,8 @@ import 'package:yimaru_app/services/dio_service.dart';
MaterialRoute(page: LearnView),
MaterialRoute(page: LearnLevelView),
MaterialRoute(page: LearnModuleView),
MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView),
// @stacked-route
],
dependencies: [
@ -60,6 +65,7 @@ import 'package:yimaru_app/services/dio_service.dart';
LazySingleton(classType: ApiService),
LazySingleton(classType: SecureStorageService),
LazySingleton(classType: DioService),
LazySingleton(classType: StatusCheckerService),
// @stacked-service
],
bottomsheets: [

View File

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

View File

@ -5,12 +5,13 @@
// **************************************************************************
// 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: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'
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'
as _i13;
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;
import 'package:yimaru_app/ui/views/terms_and_conditions/terms_and_conditions_view.dart'
as _i16;
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart' as _i22;
class Routes {
static const homeView = '/home-view';
@ -79,6 +81,10 @@ class Routes {
static const learnModuleView = '/learn-module-view';
static const welcomeView = '/welcome-view';
static const assessmentView = '/assessment-view';
static const all = <String>{
homeView,
onboardingView,
@ -100,6 +106,8 @@ class Routes {
learnView,
learnLevelView,
learnModuleView,
welcomeView,
assessmentView,
};
}
@ -185,129 +193,154 @@ class StackedRouter extends _i1.RouterBase {
Routes.learnModuleView,
page: _i21.LearnModuleView,
),
_i1.RouteDef(
Routes.welcomeView,
page: _i22.WelcomeView,
),
_i1.RouteDef(
Routes.assessmentView,
page: _i23.AssessmentView,
),
];
final _pagesMap = <Type, _i1.StackedRouteFactory>{
_i2.HomeView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i2.HomeView(),
settings: data,
);
},
_i3.OnboardingView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i3.OnboardingView(),
settings: data,
);
},
_i4.StartupView: (data) {
return _i22.MaterialPageRoute<dynamic>(
builder: (context) => const _i4.StartupView(),
final args = data.getArgs<StartupViewArguments>(
orElse: () => const StartupViewArguments(),
);
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
settings: data,
);
},
_i5.ProfileView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i5.ProfileView(),
settings: data,
);
},
_i6.ProfileDetailView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i6.ProfileDetailView(),
settings: data,
);
},
_i7.DownloadsView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i7.DownloadsView(),
settings: data,
);
},
_i8.ProgressView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i8.ProgressView(),
settings: data,
);
},
_i9.OngoingProgressView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i9.OngoingProgressView(),
settings: data,
);
},
_i10.AccountPrivacyView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i10.AccountPrivacyView(),
settings: data,
);
},
_i11.SupportView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i11.SupportView(),
settings: data,
);
},
_i12.TelegramSupportView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i12.TelegramSupportView(),
settings: data,
);
},
_i13.CallSupportView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i13.CallSupportView(),
settings: data,
);
},
_i14.LanguageView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i14.LanguageView(),
settings: data,
);
},
_i15.PrivacyPolicyView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i15.PrivacyPolicyView(),
settings: data,
);
},
_i16.TermsAndConditionsView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i16.TermsAndConditionsView(),
settings: data,
);
},
_i17.RegisterView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i17.RegisterView(),
settings: data,
);
},
_i18.LoginView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i18.LoginView(),
settings: data,
);
},
_i19.LearnView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i19.LearnView(),
settings: data,
);
},
_i20.LearnLevelView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i20.LearnLevelView(),
settings: data,
);
},
_i21.LearnModuleView: (data) {
return _i22.MaterialPageRoute<dynamic>(
return _i24.MaterialPageRoute<dynamic>(
builder: (context) => const _i21.LearnModuleView(),
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
@ -317,7 +350,61 @@ class StackedRouter extends _i1.RouterBase {
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([
int? routerId,
bool preventDuplicates = true,
@ -346,14 +433,17 @@ extension NavigatorStateExtension on _i23.NavigationService {
transition: transition);
}
Future<dynamic> navigateToStartupView([
Future<dynamic> navigateToStartupView({
_i24.Key? key,
String label = 'Loading',
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
]) async {
}) async {
return navigateTo<dynamic>(Routes.startupView,
arguments: StartupViewArguments(key: key, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -598,6 +688,37 @@ extension NavigatorStateExtension on _i23.NavigationService {
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([
int? routerId,
bool preventDuplicates = true,
@ -626,14 +747,17 @@ extension NavigatorStateExtension on _i23.NavigationService {
transition: transition);
}
Future<dynamic> replaceWithStartupView([
Future<dynamic> replaceWithStartupView({
_i24.Key? key,
String label = 'Loading',
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
]) async {
}) async {
return replaceWith<dynamic>(Routes.startupView,
arguments: StartupViewArguments(key: key, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -877,4 +1001,35 @@ extension NavigatorStateExtension on _i23.NavigationService {
parameters: parameters,
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(
initialRoute: Routes.startupView,
theme: ThemeData(fontFamily: 'Aeonik'),
onGenerateRoute: StackedRouter().onGenerateRoute,
navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
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')
final int? userId;
final bool? profileCompleted;
@JsonKey(name: 'access_token')
final String? accessToken;
@JsonKey(name: 'refresh_token')
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) =>
_$UserModelFromJson(json);

View File

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

View File

@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:yimaru_app/models/assessment.dart';
import 'package:yimaru_app/models/user_model.dart';
import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
@ -9,32 +10,12 @@ import '../ui/common/enmus.dart';
class ApiService {
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
Future<Map<String, dynamic>> register(Map<String, dynamic> data) async {
try {
Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kRegisterUrl',
data: data,
options: _getOptions(),
);
if (response.statusCode == 200) {
@ -62,7 +43,6 @@ class ApiService {
Response response = await _service.dio.post(
'$baseUrl/$kLoginUrl',
data: data,
options: _getOptions(),
);
if (response.statusCode == 200) {
@ -91,7 +71,6 @@ class ApiService {
Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kVerifyOtpUrl',
data: data,
options: _getOptions(),
);
if (response.statusCode == 200) {
return {
@ -119,7 +98,6 @@ class ApiService {
Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kResendOtpUrl',
data: data,
options: _getOptions(),
);
if (response.statusCode == 200) {
@ -146,7 +124,6 @@ class ApiService {
try {
Response response = await _service.dio.get(
'$baseUrl/$userUrl/${user?.userId}/$kProfileStatusUrl',
options: _getOptions(token: user?.accessToken),
);
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;
}
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 {
await _secureService.setInt('userId', data['userId']);
await _secureService.setString('accessToken', data['accessToken']);
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 {
UserModel user = UserModel(
userId: await _secureService.getInt('userId'),
accessToken: await _secureService.getString('accessToken'),
refreshToken: await _secureService.getString('refreshToken'));
refreshToken: await _secureService.getString('refreshToken'),
profileCompleted: await _secureService.getBool('profileCompleted'),
);
return user;
}
Future<void> logOut() async {
bool firstTimeInstall = await isFirstTimeInstall();
await _secureService.clear();
await setFirstTimeInstall(firstTimeInstall);
}
}

View File

@ -1,41 +1,175 @@
import 'package:dio/dio.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';
class DioService {
final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
final Dio _dio = Dio();
final Dio _refreshDio = Dio(); // separate instance
bool _isRefreshing = false;
final List<void Function()> _retryQueue = [];
DioService() {
_dio.options.baseUrl = baseUrl;
_dio.options.connectTimeout = const Duration(seconds: 30);
_dio.options.receiveTimeout = const Duration(seconds: 30);
_dio.options
..baseUrl = baseUrl
..connectTimeout = const Duration(seconds: 30)
..receiveTimeout = const Duration(seconds: 30);
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
debugPrint('➡️➡️➡️ REQUEST ➡️➡️➡️');
debugPrint('➡️ Data: ${options.data}');
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);
},
onError: _onError,
onRequest: _onRequest,
onResponse: _onResponse,
),
);
}
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;
}

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 = 'https://api.yimaru.yaltopia.com';
String baseUrl = 'http://195.35.29.82:8080';
//String baseUrl = 'https://api.yimaru.yaltopia.com';
String userUrl = 'api/v1/user';
@ -9,6 +9,10 @@ String kVerifyOtpUrl = 'verify-otp';
String kResendOtpUrl = 'resend-otp';
String kRefreshTokenUrl = 'api/v1/auth/refresh';
String kLoginUrl = 'api/v1/auth/customer-login';
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 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,
style: ToastificationStyle.fillColored,
description: buildToastDescription(message),
autoCloseDuration: const Duration(seconds: 10),
borderSide: const BorderSide(color: kcWhite),
autoCloseDuration: const Duration(seconds: 5),
margin: const EdgeInsets.symmetric(horizontal: 15),
);
}
@ -269,7 +270,8 @@ void showSuccessToast(String message) {
alignment: Alignment.bottomCenter,
style: ToastificationStyle.fillColored,
description: buildToastDescription(message),
autoCloseDuration: const Duration(seconds: 10),
borderSide: const BorderSide(color: kcWhite),
autoCloseDuration: const Duration(seconds: 5),
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:yimaru_app/ui/common/app_colors.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/views/onboarding/onboarding_viewmodel.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});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildAppBar() => const LargeAppBar(
@ -32,30 +32,30 @@ class AssessmentCompletionScreen extends ViewModelWidget<OnboardingViewModel> {
showLanguageSelection: false,
);
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
@ -84,12 +84,12 @@ class AssessmentCompletionScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/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';
class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> {
import '../assessment_viewmodel.dart';
class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentFailureScreen({super.key});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
@ -86,18 +85,18 @@ class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column(
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
@ -108,12 +107,12 @@ class AssessmentFailureScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(OnboardingViewModel viewModel) =>
Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/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';
class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> {
import '../assessment_viewmodel.dart';
class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentIntroScreen({super.key});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Want a quick assessment to know your English level?',
@ -78,18 +77,18 @@ class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column(
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
@ -100,18 +99,19 @@ class AssessmentIntroScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(OnboardingViewModel viewModel) =>
const CustomElevatedButton(
Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Skip',
borderRadius: 12,
borderColor: kcPrimaryColor,
backgroundColor: kcWhite,
borderColor: 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/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';
class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
import '../assessment_viewmodel.dart';
class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentResultScreen({super.key});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge,
_buildTitle(),
_buildTitle(viewModel),
verticalSpaceSmall,
_buildPrimarySubTitle(),
verticalSpaceMedium,
@ -70,10 +69,10 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSecondarySubTitle()
];
Widget _buildTitle() => const Text(
'Youre likely a B1 speaker!',
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel.name.toUpperCase()} speaker!',
textAlign: TextAlign.center,
style: TextStyle(
style: const TextStyle(
fontSize: 25,
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
@ -94,18 +93,18 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column(
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
@ -116,12 +115,12 @@ class AssessmentResultScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(OnboardingViewModel viewModel) =>
Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,

View File

@ -3,45 +3,46 @@ import 'package:flutter_svg/svg.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/views/onboarding/onboarding_viewmodel.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});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceMassive,
_buildIcon(),
verticalSpaceMedium,
@ -50,11 +51,11 @@ class ResultAnalysisScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(),
];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(language: false));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildIcon() => SvgPicture.asset(
'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/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';
class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
import '../assessment_viewmodel.dart';
class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
const RetakeAssessmentScreen({super.key});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
@ -59,11 +60,11 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(),
];
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(language: false));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildIcon() => const Icon(
Icons.warning_amber_rounded,
@ -87,18 +88,18 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLowerColumn(OnboardingViewModel viewModel) => Column(
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
@ -109,12 +110,12 @@ class RetakeAssessmentScreen extends ViewModelWidget<OnboardingViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(OnboardingViewModel viewModel) =>
Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/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';
class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> {
import '../../../common/enmus.dart';
import '../assessment_viewmodel.dart';
class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
Widget _buildAppBar() => const LargeAppBar(
@ -32,52 +47,52 @@ class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> {
showLanguageSelection: false,
);
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) => Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
_buildTitle(viewModel),
verticalSpaceSmall,
_buildSubTitle(),
];
Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg');
Widget _buildTitle() => const Text.rich(
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
TextSpan(
text: 'Welcome aboard',
style: TextStyle(
style: const TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
children: [
TextSpan(
text: ', Bisrat!',
style: TextStyle(
text: ', ${viewModel.userData['first_name']}!',
style: const TextStyle(
fontSize: 25,
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
@ -91,18 +106,18 @@ class StartLessonScreen extends ViewModelWidget<OnboardingViewModel> {
style: TextStyle(color: kcMediumGrey),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Go to My Lessons',
foregroundColor: kcWhite,
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/views/learn/learn_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 'home_viewmodel.dart';
@ -22,18 +23,26 @@ class HomeView extends StackedView<HomeViewModel> {
@override
Widget builder(
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(
body: getViewForIndex(viewModel.currentIndex),
bottomNavigationBar: BottomNavigationBar(
bottomNavigationBar: _buildBottomNav(viewModel),
);
Widget _buildBottomNav(HomeViewModel viewModel) => BottomNavigationBar(
onTap: viewModel.setCurrentIndex,
items: _buildNavBarItems(),
selectedItemColor: kcPrimaryColor,
backgroundColor: kcBackgroundColor,
type: BottomNavigationBarType.fixed,
currentIndex: viewModel.currentIndex,
),
);
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.router.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:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
@ -14,6 +15,7 @@ import '../../common/enmus.dart';
class HomeViewModel extends BaseViewModel {
final _apiService = locator<ApiService>();
final _dialogService = locator<DialogService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>();
final _authenticationService = locator<AuthenticationService>();
@ -30,16 +32,16 @@ class HomeViewModel extends BaseViewModel {
void showDialog() {
_dialogService.showCustomDialog(
variant: DialogType.infoAlert,
title: 'Stacked Rocks!',
variant: DialogType.infoAlert,
description: 'Give stacked stars on Github',
);
}
void showBottomSheet() {
_bottomSheetService.showCustomSheet(
variant: BottomSheetType.notice,
title: ksHomeBottomSheetTitle,
variant: BottomSheetType.notice,
description: ksHomeBottomSheetDescription,
);
}
@ -50,13 +52,27 @@ class HomeViewModel extends BaseViewModel {
// Remote api calls
Future<void> getProfileStatus() async {
UserModel user = await _authenticationService.getUser();
Map<String, dynamic> response = await runBusyFuture<Map<String, dynamic>>(
_apiService.getProfileStatus(user));
Map<String, dynamic> response =
await runBusyFuture<Map<String, dynamic>>(_getProfileStatus());
if (response['status'] == ResponseStatus.success && !response['data']) {
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:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/register/register_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_cursor.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/large_app_bar.dart';
import '../login_viewmodel.dart';
import '../login_view.form.dart';

View File

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

View File

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

View File

@ -1,30 +1,18 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.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/assessment/assessment_intro_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/assessment_result_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/first_assessment_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/assessment/result_analysis_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/retake_assessment_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/second_assessment_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/start_lesson_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/assessment/third_assessment_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/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 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/birthday_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/country_region_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/full_name_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/gender_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart';
import '../../common/validators/form_validator.dart';
import 'onboarding_viewmodel.dart';
@ -35,7 +23,7 @@ import 'onboarding_view.form.dart';
FormTextField(name: 'fullName', validator: FormValidator.validateForm),
FormTextField(name: 'challenge', 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),
])
class OnboardingView extends StackedView<OnboardingViewModel>
@ -70,8 +58,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
PopScope(
canPop: false,
onPopInvokedWithResult: (value, data) => viewModel.pop(
language: viewModel.currentPage == 23 ? true : false),
onPopInvokedWithResult: (value, data) => viewModel.pop(),
child: _buildOnboardingScreens(viewModel));
Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
@ -80,46 +67,31 @@ class OnboardingView extends StackedView<OnboardingViewModel>
);
List<Widget> _buildScreens() => [
_buildFirstWelcome(),
_buildSecondWelcome(),
_buildThirdWelcome(),
_buildFullNameForm(),
_buildEducationalBackgroundForm(),
_buildGenderForm(),
_buildBirthdayForm(),
_buildAgeGroupForm(),
_buildEducationalBackgroundForm(),
_buildOccupationForm(),
_buildCountryRegionForm(),
_buildLearningGoalForm(),
_buildLearningReasonForm(),
_buildLanguageGoalForm(),
_buildChallengeForm(),
_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() =>
FullNameFormScreen(fullNameController: fullNameController);
Widget _buildEducationalBackgroundForm() =>
const EducationalBackgroundFormScreen();
Widget _buildGenderForm() => const GenderFormScreen();
Widget _buildBirthdayForm() => const BirthdayFormScreen();
Widget _buildAgeGroupForm() => const AgeGroupFormScreen();
Widget _buildEducationalBackgroundForm() =>
const EducationalBackgroundFormScreen();
Widget _buildOccupationForm() =>
OccupationFormScreen(occupationController: occupationController);
@ -127,36 +99,11 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildLearningGoalForm() => const LearningGoalFormScreen();
Widget _buildLearningReasonForm() => LearningReasonFormScreen(
learningReasonController: learningReasonController);
Widget _buildLanguageGoalForm() =>
LanguageGoalFormScreen(languageGoalController: languageGoalController);
Widget _buildChallengeForm() =>
ChallengeFormScreen(challengeController: challengeController);
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 ChallengeValueKey = 'challenge';
const String OccupationValueKey = 'occupation';
const String LearningReasonValueKey = 'learningReason';
const String LanguageGoalValueKey = 'languageGoal';
const String TopicValueKey = 'topic';
final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
@ -29,7 +29,7 @@ final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
FullNameValueKey: FormValidator.validateForm,
ChallengeValueKey: FormValidator.validateForm,
OccupationValueKey: FormValidator.validateForm,
LearningReasonValueKey: FormValidator.validateForm,
LanguageGoalValueKey: FormValidator.validateForm,
TopicValueKey: FormValidator.validateForm,
};
@ -42,8 +42,8 @@ mixin $OnboardingView {
_getFormTextEditingController(ChallengeValueKey);
TextEditingController get occupationController =>
_getFormTextEditingController(OccupationValueKey);
TextEditingController get learningReasonController =>
_getFormTextEditingController(LearningReasonValueKey);
TextEditingController get languageGoalController =>
_getFormTextEditingController(LanguageGoalValueKey);
TextEditingController get topicController =>
_getFormTextEditingController(TopicValueKey);
@ -51,8 +51,8 @@ mixin $OnboardingView {
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
FocusNode get learningReasonFocusNode =>
_getFormFocusNode(LearningReasonValueKey);
FocusNode get languageGoalFocusNode =>
_getFormFocusNode(LanguageGoalValueKey);
FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey);
TextEditingController _getFormTextEditingController(
@ -83,7 +83,7 @@ mixin $OnboardingView {
fullNameController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
learningReasonController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
topicController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
@ -100,7 +100,7 @@ mixin $OnboardingView {
fullNameController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
learningReasonController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
topicController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
@ -115,7 +115,7 @@ mixin $OnboardingView {
FullNameValueKey: fullNameController.text,
ChallengeValueKey: challengeController.text,
OccupationValueKey: occupationController.text,
LearningReasonValueKey: learningReasonController.text,
LanguageGoalValueKey: languageGoalController.text,
TopicValueKey: topicController.text,
}),
);
@ -163,8 +163,8 @@ extension ValueProperties on FormStateHelper {
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
String? get occupationValue =>
this.formValueMap[OccupationValueKey] as String?;
String? get learningReasonValue =>
this.formValueMap[LearningReasonValueKey] as String?;
String? get languageGoalValue =>
this.formValueMap[LanguageGoalValueKey] as String?;
String? get topicValue => this.formValueMap[TopicValueKey] as String?;
set answerValue(String? value) {
@ -210,14 +210,14 @@ extension ValueProperties on FormStateHelper {
}
}
set learningReasonValue(String? value) {
set languageGoalValue(String? value) {
this.setData(
this.formValueMap..addAll({LearningReasonValueKey: value}),
this.formValueMap..addAll({LanguageGoalValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(
LearningReasonValueKey)) {
_OnboardingViewTextEditingControllers[LearningReasonValueKey]?.text =
LanguageGoalValueKey)) {
_OnboardingViewTextEditingControllers[LanguageGoalValueKey]?.text =
value ?? '';
}
}
@ -244,9 +244,9 @@ extension ValueProperties on FormStateHelper {
bool get hasOccupation =>
this.formValueMap.containsKey(OccupationValueKey) &&
(occupationValue?.isNotEmpty ?? false);
bool get hasLearningReason =>
this.formValueMap.containsKey(LearningReasonValueKey) &&
(learningReasonValue?.isNotEmpty ?? false);
bool get hasLanguageGoal =>
this.formValueMap.containsKey(LanguageGoalValueKey) &&
(languageGoalValue?.isNotEmpty ?? false);
bool get hasTopic =>
this.formValueMap.containsKey(TopicValueKey) &&
(topicValue?.isNotEmpty ?? false);
@ -259,9 +259,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false;
bool get hasOccupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false;
bool get hasLearningReasonValidationMessage =>
this.fieldsValidationMessages[LearningReasonValueKey]?.isNotEmpty ??
false;
bool get hasLanguageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey]?.isNotEmpty ?? false;
bool get hasTopicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
@ -273,8 +272,8 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey];
String? get occupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey];
String? get learningReasonValidationMessage =>
this.fieldsValidationMessages[LearningReasonValueKey];
String? get languageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey];
String? get topicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey];
}
@ -288,8 +287,8 @@ extension Methods on FormStateHelper {
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
setLearningReasonValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LearningReasonValueKey] = validationMessage;
setLanguageGoalValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage;
setTopicValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[TopicValueKey] = validationMessage;
@ -299,7 +298,7 @@ extension Methods on FormStateHelper {
fullNameValue = '';
challengeValue = '';
occupationValue = '';
learningReasonValue = '';
languageGoalValue = '';
topicValue = '';
}
@ -310,7 +309,7 @@ extension Methods on FormStateHelper {
FullNameValueKey: getValidationMessage(FullNameValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
LearningReasonValueKey: getValidationMessage(LearningReasonValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
TopicValueKey: getValidationMessage(TopicValueKey),
});
}
@ -335,6 +334,6 @@ void updateValidationData(FormStateHelper model) =>
FullNameValueKey: getValidationMessage(FullNameValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
LearningReasonValueKey: getValidationMessage(LearningReasonValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
TopicValueKey: getValidationMessage(TopicValueKey),
});

View File

@ -2,6 +2,7 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart';
import '../home/home_view.dart';
class OnboardingViewModel extends FormViewModel {
final _navigationService = locator<NavigationService>();
@ -34,6 +35,23 @@ class OnboardingViewModel extends FormViewModel {
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
final List<String> _ageGroups = [
'8-14',
@ -53,6 +71,36 @@ class OnboardingViewModel extends FormViewModel {
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
String? _selectedLearningGoal;
@ -79,19 +127,19 @@ class OnboardingViewModel extends FormViewModel {
List<Map<String, dynamic>> get learningGoals => _learningGoals;
// 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',
'Travel or handle daily situations',
'Connect with family or friends',
@ -99,7 +147,7 @@ class OnboardingViewModel extends FormViewModel {
'Other'
];
List<String> get learningReasons => _learningReasons;
List<String> get languageGoals => _languageGoals;
// Challenges
bool _showChallengeTextBox = false;
@ -147,53 +195,6 @@ class OnboardingViewModel extends FormViewModel {
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
final List<Map<String, dynamic>> _languages = [
{'code': 'አማ', 'language': 'አማርኛ'},
@ -209,6 +210,11 @@ class OnboardingViewModel extends FormViewModel {
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
// User data
final Map<String, dynamic> _userData = {};
Map<String, dynamic> get userData => _userData;
// Full name
void setFullNameFocus() {
_focusFullName = true;
@ -216,21 +222,35 @@ class OnboardingViewModel extends FormViewModel {
}
// Education background
void setSelectedEducationalBackground(String title) {
_selectedEducationalBackground = title;
void setSelectedEducationalBackground(String value) {
_selectedEducationalBackground = value;
rebuildUi();
}
bool isSelectedEducationalBackground(String title) =>
_selectedEducationalBackground == title;
bool isSelectedEducationalBackground(String value) =>
_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
void setSelectedAgeGroup(String title) {
_selectedAgeGroup = title;
void setSelectedAgeGroup(String value) {
_selectedAgeGroup = value;
rebuildUi();
}
bool isSelectedAgeGroup(String title) => _selectedAgeGroup == title;
bool isSelectedAgeGroup(String value) => _selectedAgeGroup == value;
// Occupation
void setOccupationFocus() {
@ -239,41 +259,45 @@ class OnboardingViewModel extends FormViewModel {
}
// Country
Future<List<String>> getCountries() async => ['Ethiopia', 'Djibouti'];
void setSelectedCountry(String value) {
_selectedCountry = value;
rebuildUi();
}
// Region
Future<List<String>> getRegions(String country) async =>
['Addis Ababa', 'Oromia'];
void setSelectedRegion(String value) {
_selectedRegion = value;
rebuildUi();
}
// Learning goal
void setSelectedLearningGoal(String title) {
_selectedLearningGoal = title;
void setSelectedLearningGoal(String value) {
_selectedLearningGoal = value;
rebuildUi();
}
bool isSelectedLearningGoal(String title) => _selectedLearningGoal == title;
bool isSelectedLearningGoal(String value) => _selectedLearningGoal == value;
// Learning reason
void setLearningReasonFocus() {
_focusLearningReason = true;
void setLanguageGoalFocus() {
_focusLanguageGoal = true;
rebuildUi();
}
void setSelectedLearningReason(String title) {
_selectedLearningReason = title;
if (title.toLowerCase() == 'other') {
_showReasonTextBox = true;
void setSelectedLanguageGoal(String value) {
_selectedLanguageGoal = value;
if (value.toLowerCase() == 'other') {
_showLanguageGoalTextBox = true;
} else {
if (_showReasonTextBox) {
_showReasonTextBox = false;
_focusLearningReason = false;
if (_showLanguageGoalTextBox) {
_showLanguageGoalTextBox = false;
_focusLanguageGoal = false;
}
}
rebuildUi();
}
bool isSelectedLearningReason(String title) =>
_selectedLearningReason == title;
bool isSelectedLanguageGoal(String value) => _selectedLanguageGoal == value;
// Challenges
void setChallengesFocus() {
@ -281,9 +305,9 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi();
}
void setSelectedChallenge(String title) {
_selectedChallenge = title;
if (title.toLowerCase() == 'other') {
void setSelectedChallenge(String value) {
_selectedChallenge = value;
if (value.toLowerCase() == 'other') {
_showChallengeTextBox = true;
} else {
if (_showChallengeTextBox) {
@ -294,7 +318,7 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi();
}
bool isSelectedChallenge(String title) => _selectedChallenge == title;
bool isSelectedChallenge(String value) => _selectedChallenge == value;
// Topics
void setTopicsFocus() {
@ -302,9 +326,9 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi();
}
void setSelectedTopic(String title) {
_selectedTopic = title;
if (title.toLowerCase() == 'other') {
void setSelectedTopic(String value) {
_selectedTopic = value;
if (value.toLowerCase() == 'other') {
_showTopicTextBox = true;
} else {
if (_showTopicTextBox) {
@ -315,50 +339,37 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi();
}
bool isSelectedTopic(String title) => _selectedTopic == title;
// 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;
bool isSelectedTopic(String value) => _selectedTopic == value;
// Language
void setSelectedLanguage(Map<String, dynamic> title) {
_selectedLanguage = title;
void setSelectedLanguage(Map<String, dynamic> value) {
_selectedLanguage = value;
rebuildUi();
}
bool isSelectedLanguage(String title) =>
_selectedLanguage['language'] == title;
bool isSelectedLanguage(String value) =>
_selectedLanguage['language'] == value;
// Add user data
void addUserData(Map<String, dynamic> data) {
_userData.addAll(data);
print('User data : $_userData');
}
void clearUserData() {
_userData.clear();
}
// 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 {
if (page == null) {
@ -366,11 +377,6 @@ class OnboardingViewModel extends FormViewModel {
_currentPage = _previousPage;
} else {
_currentPage++;
if (_currentPage == 19) {
rebuildUi();
await Future.delayed(const Duration(seconds: 3));
_currentPage++;
}
}
} else {
_previousPage = _currentPage;
@ -379,14 +385,13 @@ class OnboardingViewModel extends FormViewModel {
rebuildUi();
}
void pop({bool language = false}) {
if (!language) {
_currentPage--;
void pop() {
if (_currentPage == 8) {
_navigationService.back();
} else {
_currentPage = _previousPage;
_previousPage = 0;
}
_currentPage--;
rebuildUi();
}
}
}

View File

@ -10,6 +10,15 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -34,6 +43,7 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView(
child: _buildBodyWrapper(viewModel),
);
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
@ -67,10 +77,8 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Which age range are you in?',
@ -123,6 +131,6 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
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:intl/intl.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 FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
const FourthAssessmentFormScreen({super.key});
import '../../../widgets/birthday_selector.dart';
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -25,7 +39,7 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
@ -53,45 +67,38 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
verticalSpaceMedium,
_buildAnswers(viewModel)
_buildBirthdayFormField(viewModel)
];
Widget _buildAppBar() => const LargeAppBar(
showBackButton: false,
showLanguageSelection: false,
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Q4.  Choose the word that best matches the meaning of meticulous:',
'Pick your birthday?',
style: TextStyle(
fontSize: 16,
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
);
Widget _buildAnswers(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true,
itemCount: viewModel.fourthAnswers.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAnswer(
title: viewModel.fourthAnswers[index],
selected:
viewModel.isSelectedA4Answer(viewModel.fourthAnswers[index]),
onTap: () =>
viewModel.setSelectedA4Answer(viewModel.fourthAnswers[index]),
),
Widget _buildSubTitle() => const Text(
'Well personalize your learning experience based on your birthday.',
style: TextStyle(color: kcMediumGrey),
);
Widget _buildAnswer(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
CustomSmallRadioButton(
title: title,
onTap: onTap,
selected: selected,
Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
BirthdaySelector(
birthday: viewModel.selectedBirthday,
onSelected: (value) =>
viewModel.setBirthday(DateFormat('yyyy-MM-dd').format(value)),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
@ -105,10 +112,10 @@ class FourthAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap:
viewModel.selectedA4Answer != null ? () => viewModel.next() : null,
backgroundColor: viewModel.selectedA4Answer != null
backgroundColor: viewModel.selectedBirthday != null
? kcPrimaryColor
: 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});
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -37,6 +49,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView(
child: _buildBodyWrapper(viewModel),
);
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
@ -80,10 +93,8 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'What challenge do you face most with English?',
@ -160,15 +171,14 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
foregroundColor: kcWhite,
onTap: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other'
? viewModel.focusChallenge
? () => viewModel.next()
? challengeController.text.isNotEmpty
? () => _next(viewModel)
: null
: () => viewModel.next()
: () => _next(viewModel)
: null,
backgroundColor: viewModel.selectedChallenge != null
? viewModel.selectedChallenge?.toLowerCase() == 'other'
? viewModel.focusChallenge &&
challengeController.text.isNotEmpty
? challengeController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor

View File

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

View File

@ -11,6 +11,17 @@ class EducationalBackgroundFormScreen
extends ViewModelWidget<OnboardingViewModel> {
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -69,10 +80,8 @@ class EducationalBackgroundFormScreen
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Whats your current educational level?',
@ -124,7 +133,7 @@ class EducationalBackgroundFormScreen
borderRadius: 12,
foregroundColor: kcWhite,
onTap: viewModel.selectedEducationalBackground != null
? () => viewModel.next()
? () => _next(viewModel)
: null,
backgroundColor: viewModel.selectedEducationalBackground != null
? kcPrimaryColor

View File

@ -1,18 +1,28 @@
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/helper_functions.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';
import '../onboarding_view.form.dart';
class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController 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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -74,10 +84,8 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: false,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'What should we call you? 😊',
@ -128,11 +136,9 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: viewModel.focusFullName && fullNameController.text.isNotEmpty
? () => viewModel.next()
: null,
backgroundColor:
viewModel.focusFullName && fullNameController.text.isNotEmpty
onTap:
fullNameController.text.isNotEmpty ? () => _next(viewModel) : null,
backgroundColor: fullNameController.text.isNotEmpty
? kcPrimaryColor
: 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/large_app_bar.dart';
class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
const ThirdAssessmentFormScreen({super.key});
class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -25,7 +34,7 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)];
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildExpandedBody(OnboardingViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
@ -53,37 +62,45 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubTitle(),
verticalSpaceMedium,
_buildAnswers(viewModel)
_buildAgeGroups(viewModel)
];
Widget _buildAppBar() => const LargeAppBar(
showBackButton: false,
showLanguageSelection: false,
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Q3. Which word means the same as expand?',
'Choose your gender?',
style: TextStyle(
fontSize: 16,
fontSize: 25,
color: kcDarkGrey,
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,
itemCount: viewModel.thirdAnswers.length,
itemCount: viewModel.genders.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildAnswer(
title: viewModel.thirdAnswers[index],
selected: viewModel.isSelectedA3Answer(viewModel.thirdAnswers[index]),
onTap: () =>
viewModel.setSelectedA3Answer(viewModel.thirdAnswers[index]),
itemBuilder: (context, index) => _buildAgeGroup(
title: viewModel.genders[index],
selected: viewModel.isSelectedGender(viewModel.genders[index]),
onTap: () => viewModel.setSelectedGender(viewModel.genders[index]),
),
);
Widget _buildAnswer(
Widget _buildAgeGroup(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
@ -104,10 +121,9 @@ class ThirdAssessmentFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: viewModel.selectedA3Answer != null
backgroundColor: viewModel.selectedGender != null
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap:
viewModel.selectedA3Answer != null ? () => viewModel.next() : null,
onTap: viewModel.selectedGender != null ? () => _next(viewModel) : 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/large_app_bar.dart';
class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController learningReasonController;
class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController languageGoalController;
const LearningReasonFormScreen(
{super.key, required this.learningReasonController});
const LanguageGoalFormScreen(
{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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
@ -66,14 +78,14 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildSubTitle(),
verticalSpaceMedium,
_buildReasons(viewModel),
if (viewModel.showReasonTextBox) _buildReasonFormField(viewModel),
if (viewModel.showReasonTextBox &&
viewModel.hasLearningReasonValidationMessage &&
viewModel.focusLearningReason)
if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLanguageGoal)
verticalSpaceTiny,
if (viewModel.showReasonTextBox &&
viewModel.hasLearningReasonValidationMessage &&
viewModel.focusLearningReason)
if (viewModel.showLanguageGoalTextBox &&
viewModel.hasLanguageGoalValidationMessage &&
viewModel.focusLanguageGoal)
_buildReasonValidatorWrapper(viewModel),
verticalSpaceMedium,
];
@ -82,8 +94,8 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(language: false));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Whats your main goal for improving your English?',
@ -104,18 +116,18 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: viewModel.learningReasons.length,
itemCount: viewModel.languageGoals.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildLearningReason(
title: viewModel.learningReasons[index],
selected: viewModel
.isSelectedLearningReason(viewModel.learningReasons[index]),
onTap: () => viewModel
.setSelectedLearningReason(viewModel.learningReasons[index]),
itemBuilder: (context, index) => _buildLanguageGoal(
title: viewModel.languageGoals[index],
selected:
viewModel.isSelectedLanguageGoal(viewModel.languageGoals[index]),
onTap: () =>
viewModel.setSelectedLanguageGoal(viewModel.languageGoals[index]),
),
);
Widget _buildLearningReason(
Widget _buildLanguageGoal(
{required String title,
required bool selected,
required GestureTapCallback onTap}) =>
@ -127,21 +139,21 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget _buildReasonFormField(OnboardingViewModel viewModel) => TextFormField(
maxLines: 3,
controller: learningReasonController,
onTap: viewModel.setLearningReasonFocus,
controller: languageGoalController,
onTap: viewModel.setLanguageGoalFocus,
decoration: inputDecoration(
focus: true,
hint: 'Write your goal…',
filled: learningReasonController.text.isNotEmpty),
filled: languageGoalController.text.isNotEmpty),
);
Widget _buildReasonValidatorWrapper(OnboardingViewModel viewModel) =>
viewModel.hasLearningReasonValidationMessage
viewModel.hasLanguageGoalValidationMessage
? _buildReasonValidator(viewModel)
: Container();
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
viewModel.learningReasonValidationMessage!,
viewModel.languageGoalValidationMessage!,
style: const TextStyle(
fontSize: 12,
color: Colors.red,
@ -160,17 +172,16 @@ class LearningReasonFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: viewModel.selectedLearningReason != null
? viewModel.selectedLearningReason?.toLowerCase() == 'other'
? viewModel.focusLearningReason
? () => viewModel.next()
onTap: viewModel.selectedLanguageGoal != null
? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? languageGoalController.text.isNotEmpty
? () => _next(viewModel)
: null
: () => viewModel.next()
: () => _next(viewModel)
: null,
backgroundColor: viewModel.selectedLearningReason != null
? viewModel.selectedLearningReason?.toLowerCase() == 'other'
? viewModel.focusLearningReason &&
learningReasonController.text.isNotEmpty
backgroundColor: viewModel.selectedLanguageGoal != null
? viewModel.selectedLanguageGoal?.toLowerCase() == 'other'
? languageGoalController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: 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;
}
Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = {
'learning_goal': viewModel.selectedLearningGoal,
};
viewModel.addUserData(data);
viewModel.next();
}
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -47,6 +58,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
SingleChildScrollView(
child: _buildBodyWrapper(viewModel),
);
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
@ -69,7 +81,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(),
_buildTitle(viewModel),
verticalSpaceMedium,
_buildLearningGoals(viewModel)
];
@ -78,14 +90,12 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
onPop: viewModel.pop,
showBackButton: true,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Hi Johnny, Choose your learning goal.',
style: TextStyle(
Widget _buildTitle(OnboardingViewModel viewModel) => Text(
'Hi ${viewModel.userData['first_name']}, Choose your learning goal.',
style: const TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
@ -133,7 +143,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
borderRadius: 12,
foregroundColor: kcWhite,
onTap: viewModel.selectedLearningGoal != null
? () => viewModel.next()
? () => _next(viewModel)
: null,
backgroundColor: viewModel.selectedLearningGoal != null
? 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/widgets/large_app_bar.dart';
import '../../onboarding_view.form.dart';
import '../onboarding_view.form.dart';
class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
final TextEditingController 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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -77,10 +86,8 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
showBackButton: true,
onPop: viewModel.pop,
showLanguageSelection: true,
onLanguage: () => viewModel.next(page: 23),
onTap: () => viewModel.pop(
language: false,
));
onLanguage: () async => await viewModel.navigateToLanguage(),
);
Widget _buildTitle() => const Text(
'Whats your occupation?',
@ -131,11 +138,10 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: viewModel.focusOccupation && occupationController.text.isNotEmpty
? () => viewModel.next()
onTap: occupationController.text.isNotEmpty
? () => _next(viewModel)
: null,
backgroundColor:
viewModel.focusOccupation && occupationController.text.isNotEmpty
backgroundColor: occupationController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
);

View File

@ -13,6 +13,17 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
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
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
@ -30,6 +41,13 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildScaffoldChildren(OnboardingViewModel 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) =>
Expanded(child: _buildBodyScroller(viewModel));
@ -77,15 +95,6 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
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(
'Which topics interest you most?',
style: TextStyle(
@ -159,14 +168,14 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
foregroundColor: kcWhite,
onTap: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other'
? viewModel.focusTopic
? () => viewModel.next()
? topicController.text.isNotEmpty
? () async => await _next(viewModel)
: null
: () => viewModel.next()
: () async => await _next(viewModel)
: null,
backgroundColor: viewModel.selectedTopic != null
? viewModel.selectedTopic?.toLowerCase() == 'other'
? viewModel.focusTopic && topicController.text.isNotEmpty
? topicController.text.isNotEmpty
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1)
: kcPrimaryColor

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/widgets/birthday_selector.dart';
@ -323,7 +324,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
List<Widget> _buildBirthdayChildren(ProfileDetailViewModel viewModel) => [
_buildBirthdayLabel(),
verticalSpaceSmall,
_buildBirthdayFormField(),
_buildBirthdayFormField(viewModel),
];
Widget _buildBirthdayLabel() => CustomFormLabel(
@ -331,7 +332,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
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) =>
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/ui_helpers.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 '../../../models/user_model.dart';
@ -234,18 +233,20 @@ class RegisterViewModel extends FormViewModel {
Future<Map<String, dynamic>> _verifyOtp() async {
Map<String, dynamic> response = await _apiService.verifyOtp(_userData);
if (response['status'] == ResponseStatus.success) {
// UserModel user = response['data'] as UserModel;
// Map<String, dynamic> data = {
// 'userId': user.userId,
// 'accessToken': user.accessToken,
// 'refreshToken': user.refreshToken
// };
UserModel user = response['data'] as UserModel;
Map<String, dynamic> data = {
'userId': user.userId,
'accessToken': user.accessToken,
'refreshToken': user.refreshToken
};
await _authenticationService.saveUserData({
'userId': 10,
'accessToken': 'accessToken',
'refreshToken': 'refreshToken'
});
// {
// 'userId': 10,
// 'accessToken': 'accessToken',
// 'refreshToken': 'refreshToken'
// }
await _authenticationService.saveUserData(data);
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);

View File

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

View File

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

View File

@ -138,12 +138,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap:
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
onTap: phoneNumberController.text.isNotEmpty
? () => viewModel.goTo(page: 3, type: RegistrationType.phone)
: null,
backgroundColor:
viewModel.focusPhoneNumber && phoneNumberController.text.isNotEmpty
backgroundColor: phoneNumberController.text.isNotEmpty
? kcPrimaryColor
: 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:pinput/pinput.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/widgets/custom_cursor.dart';
@ -164,14 +163,12 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: viewModel.focusOtp &&
otpController.text.length == 6 &&
!viewModel.hasOtpValidationMessage
backgroundColor:
otpController.text.length == 6 && !viewModel.hasOtpValidationMessage
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.focusOtp &&
otpController.text.length == 6 &&
!viewModel.hasOtpValidationMessage
onTap:
otpController.text.length == 6 && !viewModel.hasOtpValidationMessage
? () async => await _verifyOtp(viewModel)
: null,
);

View File

@ -9,7 +9,8 @@ import '../../common/app_colors.dart';
import 'startup_viewmodel.dart';
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
Widget builder(
@ -70,7 +71,7 @@ class StartupView extends StackedView<StartupViewModel> {
];
Widget _buildLoadingText() =>
const Text('Loading ...', style: TextStyle(color: kcWhite, fontSize: 16));
Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16));
Widget _buildIndicatorWrapper() => SizedBox(
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
Future runStartupLogic() async {
final response = await _authenticationService.userLoggedIn();
if (response) {
final loggedIn = await _authenticationService.userLoggedIn();
final firstTimeInstall = await _authenticationService.isFirstTimeInstall();
if (firstTimeInstall) {
_navigationService.replaceWithWelcomeView();
} else {
if (loggedIn) {
_navigationService.replaceWithHomeView();
} else {
_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/ui_helpers.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});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack(
Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(),
_buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel)
@ -68,17 +69,17 @@ class FirstWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align(
Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel),
);
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding(
Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(WelcomeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/ui_helpers.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});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack(
Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(),
_buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel)
@ -68,17 +69,17 @@ class SecondWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align(
Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel),
);
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding(
Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50, right: 50, left: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(OnboardingViewModel viewModel) =>
Widget _buildContinueButton(WelcomeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/ui_helpers.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});
@override
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
Widget build(BuildContext context, WelcomeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(WelcomeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) => Stack(
Widget _buildScaffold(WelcomeViewModel viewModel) => Stack(
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [
List<Widget> _buildScaffoldChildren(WelcomeViewModel viewModel) => [
_buildBackground(),
_buildColumnWrapper(),
_buildContinueButtonWrapper(viewModel)
@ -52,6 +54,7 @@ class ThirdWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
verticalSpaceMedium,
_buildTitle(),
];
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
height: 50,
@ -67,24 +70,30 @@ class ThirdWelcomeScreen extends ViewModelWidget<OnboardingViewModel> {
),
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Align(
Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel),
);
Widget _buildButtonContainer(OnboardingViewModel viewModel) => Padding(
Widget _buildButtonContainer(WelcomeViewModel viewModel) => Padding(
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(
height: 55,
borderRadius: 12,
text: 'Start Learning',
backgroundColor: kcWhite,
trailingIcon: Icons.arrow_forward,
onTap: () => viewModel.next(),
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: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/ui_helpers.dart';
import 'package:omni_datetime_picker/omni_datetime_picker.dart';
class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
const BirthdaySelector({super.key});
class BirthdaySelector extends StatelessWidget {
final String? birthday;
final void Function(DateTime)? onSelected;
DateTime _initialDate(ProfileDetailViewModel viewModel) {
const BirthdaySelector({super.key, this.birthday, this.onSelected});
DateTime _initialDate() {
try {
final parsedDate = format.parse(viewModel.selectedBirthday ?? '');
final parsedDate = format.parse(birthday ?? '');
return parsedDate.isAfter(DateTime.now()) ? DateTime.now() : parsedDate;
} catch (_) {
return DateTime.now();
@ -19,8 +20,8 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
}
Future<void> _pickDateTime(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) async {
BuildContext context,
) async {
DateTime? dateTime = await showOmniDateTimePicker(
context: context,
is24HourMode: false,
@ -28,10 +29,10 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
lastDate: DateTime.now(),
firstDate: DateTime(1900),
barrierDismissible: true,
initialDate: _initialDate(),
titleSeparator: const Divider(),
padding: const EdgeInsets.all(16),
type: OmniDateTimePickerType.date,
initialDate: _initialDate(viewModel),
borderRadius: const BorderRadius.all(Radius.circular(15)),
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
title: const Text('Birthday', style: TextStyle(fontSize: 16)),
@ -42,56 +43,55 @@ class BirthdaySelector extends ViewModelWidget<ProfileDetailViewModel> {
);
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));
//onChanged(formattedDateTime);
if (onSelected != null) {
onSelected!(dateTime);
}
}
}
@override
Widget build(BuildContext context, ProfileDetailViewModel viewModel) =>
_buildButtonWrapper(context: context, viewModel: viewModel);
Widget build(
BuildContext context,
) =>
_buildButtonWrapper(
context,
);
Widget _buildButtonWrapper(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>
Container(
Widget _buildButtonWrapper(BuildContext context) => Container(
height: 50,
width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15),
child: _buildContainerWrapper(context: context, viewModel: viewModel),
child: _buildContainerWrapper(context),
);
Widget _buildContainerWrapper(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>
GestureDetector(
onTap: () async =>
await _pickDateTime(context: context, viewModel: viewModel),
child: _buildContainer(viewModel),
Widget _buildContainerWrapper(BuildContext context) => GestureDetector(
onTap: () async => await _pickDateTime(
context,
),
child: _buildContainer(),
);
Widget _buildContainer(ProfileDetailViewModel viewModel) => Container(
Widget _buildContainer() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: kcPrimaryColor.withOpacity(0.1),
border: Border.all(color: kcPrimaryColor),
),
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildButtonRowWrapper(viewModel),
child: _buildButtonRowWrapper(),
);
Widget _buildButtonRowWrapper(ProfileDetailViewModel viewModel) => Row(
Widget _buildButtonRowWrapper() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildButtonRowChildren(viewModel),
children: _buildButtonRowChildren(),
);
List<Widget> _buildButtonRowChildren(ProfileDetailViewModel viewModel) =>
[_buildText(viewModel), _buildIcon()];
List<Widget> _buildButtonRowChildren() => [_buildText(), _buildIcon()];
Widget _buildText(ProfileDetailViewModel viewModel) => Text(
viewModel.selectedBirthday ?? 'Pick birthday',
Widget _buildText() => Text(
birthday ?? 'Pick birthday',
style: const TextStyle(color: kcDarkGrey),
);

View File

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

View File

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

View File

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

View File

@ -5,10 +5,14 @@
import FlutterMacOS
import Foundation
import battery_plus
import connectivity_plus
import flutter_secure_storage_darwin
import path_provider_foundation
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"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}

View File

@ -33,6 +33,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -153,6 +169,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -185,6 +217,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.6"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio:
dependency: "direct main"
description:
@ -464,6 +504,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -608,6 +664,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -861,6 +925,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -933,6 +1005,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.1"
upower:
dependency: transitive
description:
name: upower
sha256: cf042403154751180affa1d15614db7fa50234bc2373cd21c3db666c38543ebf
url: "https://pub.dev"
source: hosted
version: "0.7.0"
uuid:
dependency: transitive
description:

View File

@ -16,8 +16,11 @@ dependencies:
iconsax: ^0.0.8
flutter_svg: ^2.2.3
stacked_shared: any
battery_plus: ^7.0.0
storage_info: ^1.0.0
flutter_html: ^3.0.0
email_validator: any
in_app_update: ^4.2.5
toastification: ^3.0.3
dropdown_search: ^6.0.2
json_annotation: ^4.9.0
@ -26,6 +29,7 @@ dependencies:
json_serializable: ^6.8.0
flutter_secure_storage: ^10.0.0
flutter_timer_countdown: ^1.0.7
internet_connection_checker_plus: ^2.9.1+2
dev_dependencies:
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/secure_storage_service.dart';
import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
// @stacked-import
import 'test_helpers.mocks.dart';
@ -20,6 +21,7 @@ import 'test_helpers.mocks.dart';
MockSpec<ApiService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<SecureStorageService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<DioService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<StatusCheckerService>(onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec
],
)
@ -31,6 +33,7 @@ void registerServices() {
getAndRegisterApiService();
getAndRegisterSecureStorageService();
getAndRegisterDioService();
getAndRegisterStatusCheckerService();
// @stacked-mock-register
}
@ -115,6 +118,13 @@ MockDioService getAndRegisterDioService() {
locator.registerSingleton<DioService>(service);
return service;
}
MockStatusCheckerService getAndRegisterStatusCheckerService() {
_removeRegistrationIfExists<StatusCheckerService>();
final service = MockStatusCheckerService();
locator.registerSingleton<StatusCheckerService>(service);
return service;
}
// @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() {

View File

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

View File

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