Compare commits

..

3 Commits

Author SHA1 Message Date
580b0360c2 Merge tag '0.1.6' into develop
-fix(Onboarding): Manage user data fetching mechanism
2026-04-27 00:09:20 +03:00
b97e672dfe Merge branch 'release/0.1.6'
-fix(Onboarding): Manage user data fetching mechanism
2026-04-27 00:08:42 +03:00
9932293b62 fix(Onboarding): Manage user data fetching mechanism 2026-04-27 00:07:45 +03:00
21 changed files with 140 additions and 130 deletions

View File

@ -1,5 +1,4 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/option.dart';
part 'learn_question.g.dart';

View File

@ -14,7 +14,7 @@ class VoiceRecorderService with ListenableServiceMixin {
WaveformRecorderController get waveController => _waveController;
bool _isRecording = false;
final bool _isRecording = false;
bool get isRecording => _isRecording;

View File

@ -29,7 +29,7 @@ enum DuolingoAssessments { speaking, reading, writing, listening }
enum StateObjects {
none,
courses,
homeView,
startupView,
register,
verifyOtp,
resendOtp,

View File

@ -240,8 +240,8 @@ class AssessmentViewModel extends BaseViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShow(Routes.homeView);
Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView();
@ -259,7 +259,7 @@ class AssessmentViewModel extends BaseViewModel {
await _apiService.completeProfile(_userData);
if (response['status'] == ResponseStatus.success) {
clearUserData();
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);

View File

@ -2,7 +2,6 @@ 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/enmus.dart';
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
@ -16,27 +15,12 @@ class HomeView extends StackedView<HomeViewModel> {
@override
HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
@override
void onViewModelReady(HomeViewModel viewModel) async {
// Removable
await _init(viewModel);
super.onViewModelReady(viewModel);
}
Future<void> _init(HomeViewModel viewModel) async =>
await viewModel.initialize();
@override
Widget builder(
BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffoldWrapper(viewModel);
_buildScaffold(viewModel);
Widget _buildScaffoldWrapper(HomeViewModel viewModel) =>
viewModel.busy(StateObjects.homeView)
? _buildStartUpView()
: _buildScaffold(viewModel);
Widget _buildStartUpView() => const StartupView(label: 'Checking user info');
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentPage),

View File

@ -14,12 +14,9 @@ import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
class HomeViewModel extends ReactiveViewModel {
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>();
final _authenticationService = locator<AuthenticationService>();
final _imageDownloaderService = locator<ImageDownloaderService>();
@override
List<ListenableServiceMixin> get listenableServices =>
@ -49,75 +46,9 @@ class HomeViewModel extends ReactiveViewModel {
rebuildUi();
}
// Save profile status
Future<void> saveProfileStatus(bool value) async =>
await _authenticationService.saveProfileStatus(value);
// Navigation
Future<void> replaceWithFailure() async => await _navigationService
.replaceWithFailureView(label: 'Check you internet connection');
Future<void> replaceWithOnboarding() async =>
await _navigationService.replaceWithOnboardingView();
// Remote api calls
// Initialize user data
Future<void> initialize() async =>
await runBusyFuture(_initialize(), busyObject: StateObjects.homeView);
Future<void> _initialize() async {
await _getProfileStatus();
await _getProfileData();
}
// Get profile data
Future<void> _getProfileData() async {
if (!(_user?.userInfoLoaded ?? false)) {
Map<String, dynamic> response = {};
if (_user?.profileCompleted != null &&
(_user?.profileCompleted ?? false)) {
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileData(_user?.userId);
if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User;
await _authenticationService.saveUserData(user);
String image =
await _imageDownloaderService.downloader(user.profilePicture);
await _authenticationService.saveProfilePicture(image);
}
} else {
await replaceWithFailure();
}
}
}
}
// Get profile status
Future<void> _getProfileStatus() async {
Map<String, dynamic> response = {};
if (_user?.profileCompleted == null) {
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileStatus(_user);
} else {
await Future.delayed(kDuration);
await replaceWithFailure();
}
} else if (!(_user?.profileCompleted ?? false)) {
response = {'data': false, 'status': ResponseStatus.success};
} else {
response = {'data': true, 'status': ResponseStatus.success};
}
if (response['status'] == ResponseStatus.success && !response['data']) {
await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
await saveProfileStatus(response['data']);
}
}
}

View File

@ -1,7 +1,6 @@
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';

View File

@ -5,9 +5,7 @@ import 'package:yimaru_app/ui/views/learn_practice/screens/finish_learn_practice
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_questions_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/interact_learn_practice_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_intro_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../common/app_colors.dart';

View File

@ -11,7 +11,6 @@ import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../models/learn_question.dart';
import '../../../models/question.dart';
import '../../../services/api_service.dart';
import '../../../services/audio_player_service.dart';
import '../../../services/status_checker_service.dart';
@ -96,7 +95,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
List<LearnQuestion> get questions => _questions;
// Practice answers
List<Map<String, dynamic>> _answers = [];
final List<Map<String, dynamic>> _answers = [];
List<Map<String, dynamic>> get answers => _answers;

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../../common/app_colors.dart';

View File

@ -1,14 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_question.dart';
import 'package:yimaru_app/models/practice.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_card.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/small_app_bar.dart';
import '../learn_practice_viewmodel.dart';
import 'interact_learn_practice_screen.dart';

View File

@ -154,8 +154,7 @@ class LoginViewModel extends ReactiveViewModel
}
// Navigation
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShow(Routes.homeView);
Future<void> navigateToRegister() async =>
await _navigationService.navigateToRegisterView();
@ -163,6 +162,12 @@ class LoginViewModel extends ReactiveViewModel
Future<void> navigateToForgetPassword() async =>
await _navigationService.navigateToForgetPasswordView();
Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
// Remote api calls
// Login with email
@ -182,7 +187,7 @@ class LoginViewModel extends ReactiveViewModel
await _authenticationService.saveUserCredential(data);
clearUserData();
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
@ -210,7 +215,7 @@ class LoginViewModel extends ReactiveViewModel
};
await _authenticationService.saveUserCredential(data);
clearUserData();
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
@ -251,7 +256,7 @@ class LoginViewModel extends ReactiveViewModel
};
await _authenticationService.saveUserCredential(data);
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);

View File

@ -32,7 +32,6 @@ class OnboardingView extends StackedView<OnboardingViewModel>
void _initUserData(OnboardingViewModel viewModel) {
fullNameController.text = viewModel.googleUser?.displayName ?? '';
print('Full-NAME: ${fullNameController.text}');
}
void _initClearData() {

View File

@ -614,6 +614,5 @@ class OnboardingViewModel extends ReactiveViewModel
Future<void> navigateToAssessment() async =>
await _navigationService.navigateToAssessmentView(data: _userData);
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShow(Routes.homeView);
}

View File

@ -19,8 +19,6 @@ class RegisterViewModel extends ReactiveViewModel
implements FormViewModel {
final _apiService = locator<ApiService>();
final _smartAuthService = locator<SmartAuthService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
@ -297,8 +295,8 @@ class RegisterViewModel extends ReactiveViewModel
Future<void> replaceToLogin() async =>
await _navigationService.replaceWithLoginView();
Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShow(Routes.homeView);
Future<void> replaceWithStartUp() async =>
await _navigationService.clearStackAndShow(Routes.startupView);
// Remote api calls
@ -344,7 +342,7 @@ class RegisterViewModel extends ReactiveViewModel
};
await _authenticationService.saveUserCredential(data);
clearUserData();
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
@ -367,7 +365,7 @@ class RegisterViewModel extends ReactiveViewModel
};
await _authenticationService.saveUserCredential(data);
await replaceWithHome();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);

View File

@ -6,6 +6,7 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> {
@ -18,13 +19,20 @@ class StartupView extends StackedView<StartupViewModel> {
StartupViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper();
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper() => Scaffold(
Widget _buildScaffoldWrapper(StartupViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(),
body: _buildScaffoldState(viewModel),
);
Widget _buildScaffoldState(StartupViewModel viewModel) =>
viewModel.busy(StateObjects.startupView)
? _buildStartUpView()
: _buildScaffold();
Widget _buildStartUpView() => const StartupView(label: 'Checking user info');
Widget _buildScaffold() => Stack(
children: _buildScaffoldChildren(),
);

View File

@ -5,14 +5,31 @@ import 'package:yimaru_app/services/in_app_update_service.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/image_downloader_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
class StartupViewModel extends BaseViewModel {
class StartupViewModel extends ReactiveViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>();
final _imageDownloaderService = locator<ImageDownloaderService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_authenticationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Place anything here that needs to happen before we get into the application
Future runStartupLogic() async {
@ -27,7 +44,7 @@ class StartupViewModel extends BaseViewModel {
} else {
if (loggedIn) {
await _authenticationService.getUser();
await _navigationService.replaceWithHomeView();
await _getProfileStatus();
} else {
// Removable
await _navigationService.replaceWithLoginView();
@ -35,9 +52,94 @@ class StartupViewModel extends BaseViewModel {
}
}
// Navigation
Future<void> replaceWithFailure() async => await _navigationService
.replaceWithFailureView(label: 'Check you internet connection');
Future<void> replaceWithOnboarding() async =>
await _navigationService.replaceWithOnboardingView();
Future<void> replaceWithHome() async =>
await _navigationService.replaceWithHomeView();
// Remote api calls
// In-app update
Future<void> _inAppUpdate() async {
if (await _statusChecker.checkConnection()) {
await _inAppUpdateService.checkForUpdate();
}
}
// Get profile status
Future<void> _getProfileStatus() async {
Map<String, dynamic> response = {};
if (_user?.profileCompleted == null) {
if (await _statusChecker.checkConnection()) {
print('PATH: 1');
response = await _apiService.getProfileStatus(_user);
} else {
print('PATH: 2');
await Future.delayed(kDuration);
await replaceWithFailure();
}
} else if (!(_user?.profileCompleted ?? false)) {
print('PATH: 3');
response = {'data': false, 'status': ResponseStatus.success};
} else {
print('PATH: 4');
response = {'data': true, 'status': ResponseStatus.success};
}
if (response['status'] == ResponseStatus.success && !response['data']) {
print('PATH: 5');
await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
print('PATH: 6');
await saveProfileStatus(response['data']);
await _getProfileData();
await replaceWithHome();
}
}
// Save profile status
Future<void> saveProfileStatus(bool value) async =>
await _authenticationService.saveProfileStatus(value);
// Get profile data
Future<void> _getProfileData() async {
if (!(_user?.userInfoLoaded ?? false)) {
Map<String, dynamic> response = {};
if (_user?.profileCompleted != null &&
(_user?.profileCompleted ?? false)) {
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileData(_user?.userId);
if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User;
await _authenticationService.saveUserData(user);
String image =
await _imageDownloaderService.downloader(user.profilePicture);
await _authenticationService.saveProfilePicture(image);
}
} else {
await replaceWithFailure();
}
}
}
}
}

View File

@ -1,6 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart';
import '../common/app_colors.dart';

View File

@ -51,7 +51,7 @@ class LearnPracticeCard extends ViewModelWidget<LearnPracticeViewModel> {
);
Widget _buildStartButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
const CustomElevatedButton(
height: 50,
width: 200,
borderRadius: 8,

View File

@ -4,7 +4,6 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../models/learn_practice.dart';
class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeTipSection({super.key});

View File

@ -1,9 +1,8 @@
name: yimaru_app
version: 0.1.5+7
version: 0.1.6+8
publish_to: 'none'
description: A new Flutter project.
environment:
sdk: '>=3.0.3 <4.0.0'