Compare commits

..

No commits in common. "c45f90ad82b88f3fac2eb459a188afca26a1afb4" and "12f836d4274a1ae39a9046a1e913c60b701793b3" have entirely different histories.

16 changed files with 251 additions and 135 deletions

View File

@ -37,9 +37,6 @@ class User {
@JsonKey(name: 'profile_completed')
final bool? profileCompleted;
@JsonKey(name: 'subscription_status')
final String? subscriptionStatus;
@JsonKey(name: 'profile_picture_url')
final String? profilePicture;
@ -58,7 +55,6 @@ class User {
this.profilePicture,
this.userInfoLoaded,
this.profileCompleted,
this.subscriptionStatus
});
User copyWith(

View File

@ -90,8 +90,6 @@ class AuthenticationService with ListenableServiceMixin {
await _secureService.setBool('userInfoLoaded', true);
await _secureService.setBool(
'profileCompleted', data.profileCompleted ?? false);
await _secureService.setString(
'subscriptionStatus', data.subscriptionStatus ?? '');
await _secureService.setString('email', data.email ?? '');
await _secureService.setString('region', data.region ?? '');
await _secureService.setString('gender', data.gender ?? '');
@ -101,7 +99,6 @@ class AuthenticationService with ListenableServiceMixin {
await _secureService.setString('firstName', data.firstName ?? '');
await _secureService.setString('occupation', data.occupation ?? '');
_user = User(
email: data.email,
gender: data.gender,
@ -116,7 +113,6 @@ class AuthenticationService with ListenableServiceMixin {
accessToken: _user?.accessToken,
refreshToken: _user?.refreshToken,
profileCompleted: data.profileCompleted,
subscriptionStatus: data.subscriptionStatus,
);
notifyListeners();
@ -173,7 +169,6 @@ class AuthenticationService with ListenableServiceMixin {
userInfoLoaded: await _secureService.getBool('userInfoLoaded'),
profilePicture: await _secureService.getString('profilePicture'),
profileCompleted: await _secureService.getBool('profileCompleted'),
subscriptionStatus: await _secureService.getString('subscriptionStatus'),
);
return _user;
}

View File

@ -1,3 +1,4 @@
import 'package:http/http.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/refresh_object.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
@ -41,7 +42,7 @@ class LearnService with ListenableServiceMixin {
List<LearnLesson> get lessons => _lessons;
// Learn progress
final List<ProgressSummary> _summaries = [];
List<ProgressSummary> _summaries = [];
List<ProgressSummary> get summaries => _summaries;

View File

@ -101,16 +101,4 @@ class OnboardingService with ListenableServiceMixin {
}
return false;
}
// Profile detail fields
Future<void> getProfileDetailFields() async {
_countries = await _apiService.getCountries();
_occupations = await _apiService.getOccupations();
_regions = await _apiService.getEthiopiaRegions();
notifyListeners();
}
}

View File

@ -39,7 +39,6 @@ enum StateObjects {
learnModules,
learnCourses,
profileImage,
profileDetail,
learnPrograms,
courseLessons,
profileUpdate,

View File

@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/learn_module.dart';
import 'package:yimaru_app/models/refresh_object.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import '../../../app/app.locator.dart';

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/age_group_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/challenge_form_screen.dart';
@ -12,6 +13,7 @@ import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen
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 'package:yimaru_app/ui/views/startup/startup_view.dart';
import '../../common/validators/form_validator.dart';
import 'onboarding_viewmodel.dart';

View File

@ -4,9 +4,12 @@ import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart';
import '../../../models/field_option.dart';
import '../../../services/api_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart';
import '../../../services/onboarding_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class OnboardingViewModel extends ReactiveViewModel
with FormStateHelper

View File

@ -5,6 +5,7 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.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_view.form.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';

View File

@ -5,6 +5,7 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.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_view.form.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';

View File

@ -5,6 +5,7 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.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_view.form.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';

View File

@ -7,12 +7,10 @@ import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/widgets/custom_form_label.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
import '../../../models/field_option.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../common/validators/form_validator.dart';
import '../../widgets/custom_dropdown.dart';
import '../../widgets/custom_elevated_button.dart';
import '../../widgets/image_picker_option.dart';
import '../../widgets/page_loading_indicator.dart';
@ -33,30 +31,19 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
with $ProfileDetailView {
const ProfileDetailView({Key? key}) : super(key: key);
void _setSelectedCountry(
{FieldOption? value, required ProfileDetailViewModel viewModel}) {
viewModel.setSelectedCountry(value);
if (viewModel.selectedCountry?.label?.toLowerCase().tr() != 'ethiopia') {
regionController.clear();
viewModel.unsetRegionFocus();
}
}
Future<void> _update(ProfileDetailViewModel viewModel) async {
Map<String, dynamic> data = {
'gender': viewModel.selectedGender,
'region': viewModel.dropdownRegion
? viewModel.selectedRegion?.code
? viewModel.selectedRegion
: regionController.text,
'gender': viewModel.selectedGender,
'last_name': lastNameController.text,
'country': viewModel.selectedCountry,
'first_name': firstNameController.text,
'country': viewModel.selectedCountry?.code,
'occupation': viewModel.selectedOccupation?.code,
'occupation': viewModel.selectedOccupation,
'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()),
};
viewModel.addUserData(data);
await viewModel.updateProfile();
@ -81,13 +68,10 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
void _checkRegion(ProfileDetailViewModel viewModel) {
bool region = viewModel.checkRegion(
region: viewModel.user?.region ?? '',
country: viewModel.user?.country ?? '');
region: viewModel.user?.region ?? 'Addis Ababa',
country: viewModel.user?.country ?? 'Ethiopia');
if (region) {
FieldOption? option = viewModel.regions
.where((e) => (e.code ?? '') == viewModel.user?.region)
.first;
viewModel.setSelectedRegion(option);
viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa');
} else {
regionController.text = viewModel.user?.region ?? '';
}
@ -101,17 +85,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_checkRegion(viewModel);
viewModel.clearUserData();
viewModel.setSelectedGender(viewModel.user?.gender ?? '');
viewModel.setSelectedOccupation(viewModel.occupations
.where((e) => (e.code ?? '') == viewModel.user?.occupation)
.first);
viewModel.setSelectedCountry(viewModel.countries
.where((e) => (e.code ?? '') == viewModel.user?.country)
.first);
viewModel.setSelectedOccupation(viewModel.user?.occupation ?? '');
viewModel.setSelectedCountry(viewModel.user?.country ?? 'Ethiopia');
}
@override
void onViewModelReady(ProfileDetailViewModel viewModel) async{
await viewModel.getProfileDetailFields();
void onViewModelReady(ProfileDetailViewModel viewModel) {
_onModelReady(viewModel);
syncFormWithViewModel(viewModel);
super.onViewModelReady(viewModel);
@ -134,17 +113,9 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
required ProfileDetailViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffoldState(context: context, viewModel: viewModel),
body: _buildScaffoldContainer(context: context, viewModel: viewModel),
);
Widget _buildScaffoldState(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>
viewModel.busy(StateObjects.profileDetail)
? const PageLoadingIndicator()
: _buildScaffoldContainer(context: context, viewModel: viewModel);
Widget _buildScaffoldContainer( {required BuildContext context,
required ProfileDetailViewModel viewModel}) => Container(
decoration: bgDecoration,
@ -237,7 +208,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
verticalSpaceMedium,
_buildCountryDropdownLabel(),
verticalSpaceSmall,
_buildCountryDropdown(viewModel),
// _buildCountryDropdown(viewModel),
verticalSpaceMedium,
_buildRegionFormFieldWrapper(viewModel),
verticalSpaceMedium,
@ -562,14 +533,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
label: LocaleKeys.country.tr(),
);
Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker(
icon: _buildSearchIcon(),
hint: LocaleKeys.select_country.tr(),
selectedItem: viewModel.selectedCountry,
items: (value, props) => viewModel.countries,
onChanged: (value) =>
_setSelectedCountry(value: value, viewModel: viewModel));
// Widget _buildCountryDropdown(ProfileDetailViewModel viewModel) =>
// CustomDropdownPicker(
// hint: 'Select country',
// selectedItem: viewModel.selectedCountry,
// items: (value, props) => viewModel.getCountries(),
// onChanged: (value) => viewModel.setSelectedCountry(value ?? 'Ethiopia'),
// );
Widget _buildRegionFormFieldWrapper(ProfileDetailViewModel viewModel) =>
Column(
@ -601,17 +571,19 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
);
Widget _buildRegionFormState(ProfileDetailViewModel viewModel) =>
viewModel.dropdownRegion
? _buildRegionDropDown(viewModel)
: _buildRegionFormField(viewModel);
Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker(
icon: _buildSearchIcon(),
hint: LocaleKeys.select_region.tr(),
selectedItem: viewModel.selectedRegion,
items: (value, props) => viewModel.regions,
onChanged: (value) => viewModel.setSelectedRegion(value));
// viewModel.dropdownRegion
// ? _buildRegionDropDown(viewModel)
// :
_buildRegionFormField(viewModel);
//
// Widget _buildRegionDropDown(ProfileDetailViewModel viewModel) =>
// CustomDropdownPicker(
// icon: _buildSearchIcon(),
// hint:LocaleKeys.select_region.tr(),
// selectedItem: viewModel.selectedRegion,
// items: (value, props) => viewModel.getRegions(),
// onChanged: (value) =>
// viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
TextFormField(
@ -646,7 +618,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
[
_buildOccupationDropdownLabel(),
verticalSpaceSmall,
_buildOccupationDropdown(viewModel)
// _buildOccupationDropdown(viewModel)
];
Widget _buildOccupationDropdownLabel() => CustomFormLabel(
@ -654,14 +626,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
label: LocaleKeys.occupation.tr(),
);
Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) =>
CustomDropdownPicker(
icon: _buildSearchIcon(),
hint: LocaleKeys.select_occupation.tr(),
selectedItem: viewModel.selectedOccupation,
items: (value, props) => viewModel.occupations,
onChanged: (value) => viewModel.setSelectedOccupation(value));
// Widget _buildOccupationDropdown(ProfileDetailViewModel viewModel) =>
// CustomDropdownPicker(
// icon: _buildSearchIcon(),
// hint:LocaleKeys.select_occupation.tr(),
// selectedItem: viewModel.selectedOccupation,
// items: (value, props) => viewModel.getOccupations(),
// onChanged: (value) => viewModel.setSelectedOccupation(
// value ?? 'Students (High school & University)'));
Icon _buildSearchIcon() => const Icon(
Icons.search,
color: kcPrimaryColor,

View File

@ -2,12 +2,10 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../models/field_option.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/image_picker_service.dart';
import '../../../services/onboarding_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
@ -19,8 +17,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
final _statusChecker = locator<StatusCheckerService>();
final _onboardingService = locator<OnboardingService>();
final _navigationService = locator<NavigationService>();
final _imagePickerService = locator<ImagePickerService>();
@ -62,14 +58,14 @@ class ProfileDetailViewModel extends ReactiveViewModel
bool get focusEmail => _focusEmail;
// Occupation
FieldOption? _selectedOccupation;
String _selectedOccupation = 'Students (High school & University)';
FieldOption? get selectedOccupation => _selectedOccupation;
String get selectedOccupation => _selectedOccupation;
// Country
FieldOption? _selectedCountry;
String _selectedCountry = 'Ethiopia';
FieldOption? get selectedCountry => _selectedCountry;
String get selectedCountry => _selectedCountry;
// Region
bool _focusRegion = false;
@ -80,9 +76,9 @@ class ProfileDetailViewModel extends ReactiveViewModel
bool get dropdownRegion => _dropdownRegion;
FieldOption? _selectedRegion;
String _selectedRegion = 'Addis Ababa';
FieldOption? get selectedRegion => _selectedRegion;
String get selectedRegion => _selectedRegion;
// User data
final Map<String, dynamic> _userData = {};
@ -120,26 +116,182 @@ class ProfileDetailViewModel extends ReactiveViewModel
}
// Occupation
List<FieldOption> get _occupations => _onboardingService.occupations;
List<String> getOccupations() => [
'Students (High school & University)',
'Job Seekers / Fresh Graduates',
'Working Professionals (Corporate/Office)',
'Government & NGO Workers',
'Entrepreneurs & Small Business Owners',
'Hospitality & Tourism Workers',
'Freelancers / Remote Workers (Digital Economy)'
];
List<FieldOption> get occupations => _occupations;
void setSelectedOccupation(FieldOption? value) {
void setSelectedOccupation(String value) {
_selectedOccupation = value;
rebuildUi();
}
// Country
List<FieldOption> get _countries => _onboardingService.countries;
List<String> getCountries() => [
"Afghanistan",
"Albania",
"Algeria",
"Andorra",
"Angola",
"Argentina",
"Armenia",
"Australia",
"Austria",
"Azerbaijan",
"Bahrain",
"Bangladesh",
"Belarus",
"Belgium",
"Belize",
"Benin",
"Bhutan",
"Bolivia",
"Bosnia and Herzegovina",
"Botswana",
"Brazil",
"Brunei",
"Bulgaria",
"Burkina Faso",
"Burundi",
"Cambodia",
"Cameroon",
"Canada",
"Chad",
"Chile",
"China",
"Colombia",
"Comoros",
"Congo",
"Costa Rica",
"Croatia",
"Cuba",
"Cyprus",
"Czech Republic",
"Denmark",
"Djibouti",
"Dominican Republic",
"Ecuador",
"Egypt",
"El Salvador",
"Eritrea",
"Estonia",
"Eswatini",
"Ethiopia",
"Finland",
"France",
"Gabon",
"Gambia",
"Georgia",
"Germany",
"Ghana",
"Greece",
"Guatemala",
"Guinea",
"Haiti",
"Honduras",
"Hungary",
"Iceland",
"India",
"Indonesia",
"Iran",
"Iraq",
"Ireland",
"Israel",
"Italy",
"Jamaica",
"Japan",
"Jordan",
"Kazakhstan",
"Kenya",
"Kuwait",
"Kyrgyzstan",
"Laos",
"Latvia",
"Lebanon",
"Liberia",
"Libya",
"Lithuania",
"Luxembourg",
"Madagascar",
"Malawi",
"Malaysia",
"Maldives",
"Mali",
"Malta",
"Mexico",
"Moldova",
"Monaco",
"Mongolia",
"Morocco",
"Mozambique",
"Myanmar",
"Namibia",
"Nepal",
"Netherlands",
"New Zealand",
"Nicaragua",
"Niger",
"Nigeria",
"North Korea",
"Norway",
"Oman",
"Pakistan",
"Panama",
"Paraguay",
"Peru",
"Philippines",
"Poland",
"Portugal",
"Qatar",
"Romania",
"Russia",
"Rwanda",
"Saudi Arabia",
"Senegal",
"Serbia",
"Singapore",
"Slovakia",
"Slovenia",
"Somalia",
"South Africa",
"South Korea",
"Spain",
"Sri Lanka",
"Sudan",
"Sweden",
"Switzerland",
"Syria",
"Taiwan",
"Tajikistan",
"Tanzania",
"Thailand",
"Tunisia",
"Turkey",
"Uganda",
"Ukraine",
"United Arab Emirates",
"United Kingdom",
"United States",
"Uruguay",
"Uzbekistan",
"Venezuela",
"Vietnam",
"Yemen",
"Zambia",
"Zimbabwe"
];
List<FieldOption> get countries => _countries;
void setSelectedCountry(FieldOption? value) {
void setSelectedCountry(String value) {
_selectedCountry = value;
if (value?.label?.toLowerCase().trim() == 'ethiopia') {
if (value == 'Ethiopia') {
_dropdownRegion = true;
_selectedRegion = _regions
.firstWhere((e) => e.label?.toLowerCase().trim() == 'addis ababa');
_selectedRegion = 'Addis Ababa';
} else {
_dropdownRegion = false;
}
@ -148,22 +300,34 @@ class ProfileDetailViewModel extends ReactiveViewModel
}
// Region
List<FieldOption> get _regions => _onboardingService.regions;
List<FieldOption> get regions => _regions;
void setSelectedRegion(FieldOption? value) {
_selectedRegion = value;
rebuildUi();
}
List<String> getRegions() => [
'Addis Ababa',
'Afar',
'Amhara',
'Benishangul-Gumuz',
'Central Ethiopia',
'Dire Dawa',
'Gambela',
'Harari',
'Oromia',
'Sidama',
'Somali',
'South Ethiopia',
'South West Ethiopia Peoples',
'Tigray',
];
bool checkRegion({required String region, required String country}) {
if (country.toLowerCase().contains('ethiopia')) {
return _regions.contains(region);
if (country == 'Ethiopia') {
return getRegions().contains(region);
}
return false;
}
void setSelectedRegion(String value) {
_selectedRegion = value;
rebuildUi();
}
void setRegionFocus() {
_focusRegion = true;
@ -255,12 +419,4 @@ class ProfileDetailViewModel extends ReactiveViewModel
await _apiService.updateProfileImage(data: data, userId: _user?.userId);
}
}
// Profile detail fields
Future<void> getProfileDetailFields() async =>
await runBusyFuture(_getProfileDetailFields(),busyObject: StateObjects.profileDetail);
Future<void> _getProfileDetailFields() async {
await _onboardingService.getProfileDetailFields();
}
}

View File

@ -89,7 +89,7 @@ class StartupViewModel extends ReactiveViewModel {
response = {'data': true, 'status': ResponseStatus.success};
}
if (response['status'] == ResponseStatus.success && !response['data']) {
await getOnboardingFields();
await etOnboardingFields();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
await saveProfileStatus(response['data']);
@ -134,7 +134,7 @@ class StartupViewModel extends ReactiveViewModel {
// Remote api call
// Onboarding fields
Future<void> getOnboardingFields() async {
Future<void> etOnboardingFields() async {
bool response = await _onboardingService.getOnboardingFields();
if (response) {
await replaceWithOnboarding();

View File

@ -10,6 +10,7 @@ import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
import '../common/app_colors.dart';
import '../common/helper_functions.dart';
import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart';

View File

@ -1,5 +1,5 @@
name: yimaru_app
version: 0.1.25+27
version: 0.1.24+26
publish_to: 'none'
description: A new Flutter project.
@ -9,7 +9,6 @@ environment:
dependencies:
flutter:
sdk: flutter
http: any
intl: any
dio: ^5.9.0