fix: Apply UAT comments

This commit is contained in:
BisratHailu 2026-05-09 01:04:53 +03:00
parent 92db2453c5
commit e917209d10
41 changed files with 605 additions and 456 deletions

BIN
assets/images/pattern.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -510,8 +510,8 @@ class StackedRouter extends _i1.RouterBase {
_i22.LearnLessonDetailView: (data) { _i22.LearnLessonDetailView: (data) {
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false); final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) => _i22.LearnLessonDetailView(
_i22.LearnLessonDetailView(key: args.key, lesson: args.lesson), key: args.key, lesson: args.lesson, hasPractice: args.hasPractice),
settings: data, settings: data,
); );
}, },
@ -519,7 +519,13 @@ class StackedRouter extends _i1.RouterBase {
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false); final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i23.LearnPracticeView( builder: (context) => _i23.LearnPracticeView(
key: args.key, id: args.id, practice: args.practice), key: args.key,
level: args.level,
id: args.id,
label: args.label,
title: args.title,
practice: args.practice,
subtitle: args.subtitle),
settings: data, settings: data,
); );
}, },
@ -1098,56 +1104,85 @@ class LearnLessonDetailViewArguments {
const LearnLessonDetailViewArguments({ const LearnLessonDetailViewArguments({
this.key, this.key,
required this.lesson, required this.lesson,
required this.hasPractice,
}); });
final _i37.Key? key; final _i37.Key? key;
final _i40.LearnLesson lesson; final _i40.LearnLesson lesson;
final bool hasPractice;
@override @override
String toString() { String toString() {
return '{"key": "$key", "lesson": "$lesson"}'; return '{"key": "$key", "lesson": "$lesson", "hasPractice": "$hasPractice"}';
} }
@override @override
bool operator ==(covariant LearnLessonDetailViewArguments other) { bool operator ==(covariant LearnLessonDetailViewArguments other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.key == key && other.lesson == lesson; return other.key == key &&
other.lesson == lesson &&
other.hasPractice == hasPractice;
} }
@override @override
int get hashCode { int get hashCode {
return key.hashCode ^ lesson.hashCode; return key.hashCode ^ lesson.hashCode ^ hasPractice.hashCode;
} }
} }
class LearnPracticeViewArguments { class LearnPracticeViewArguments {
const LearnPracticeViewArguments({ const LearnPracticeViewArguments({
this.key, this.key,
this.level,
required this.id, required this.id,
required this.label,
required this.title,
required this.practice, required this.practice,
required this.subtitle,
}); });
final _i37.Key? key; final _i37.Key? key;
final String? level;
final int id; final int id;
final String label;
final String title;
final _i41.LearnPractices practice; final _i41.LearnPractices practice;
final String subtitle;
@override @override
String toString() { String toString() {
return '{"key": "$key", "id": "$id", "practice": "$practice"}'; return '{"key": "$key", "level": "$level", "id": "$id", "label": "$label", "title": "$title", "practice": "$practice", "subtitle": "$subtitle"}';
} }
@override @override
bool operator ==(covariant LearnPracticeViewArguments other) { bool operator ==(covariant LearnPracticeViewArguments other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.key == key && other.id == id && other.practice == practice; return other.key == key &&
other.level == level &&
other.id == id &&
other.label == label &&
other.title == title &&
other.practice == practice &&
other.subtitle == subtitle;
} }
@override @override
int get hashCode { int get hashCode {
return key.hashCode ^ id.hashCode ^ practice.hashCode; return key.hashCode ^
level.hashCode ^
id.hashCode ^
label.hashCode ^
title.hashCode ^
practice.hashCode ^
subtitle.hashCode;
} }
} }
@ -1817,6 +1852,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnLessonDetailView({ Future<dynamic> navigateToLearnLessonDetailView({
_i37.Key? key, _i37.Key? key,
required _i40.LearnLesson lesson, required _i40.LearnLesson lesson,
required bool hasPractice,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
@ -1824,7 +1860,8 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return navigateTo<dynamic>(Routes.learnLessonDetailView, return navigateTo<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson), arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, hasPractice: hasPractice),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -1833,8 +1870,12 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnPracticeView({ Future<dynamic> navigateToLearnPracticeView({
_i37.Key? key, _i37.Key? key,
String? level,
required int id, required int id,
required String label,
required String title,
required _i41.LearnPractices practice, required _i41.LearnPractices practice,
required String subtitle,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
@ -1842,8 +1883,14 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return navigateTo<dynamic>(Routes.learnPracticeView, return navigateTo<dynamic>(Routes.learnPracticeView,
arguments: arguments: LearnPracticeViewArguments(
LearnPracticeViewArguments(key: key, id: id, practice: practice), key: key,
level: level,
id: id,
label: label,
title: title,
practice: practice,
subtitle: subtitle),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -2395,6 +2442,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnLessonDetailView({ Future<dynamic> replaceWithLearnLessonDetailView({
_i37.Key? key, _i37.Key? key,
required _i40.LearnLesson lesson, required _i40.LearnLesson lesson,
required bool hasPractice,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
@ -2402,7 +2450,8 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return replaceWith<dynamic>(Routes.learnLessonDetailView, return replaceWith<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson), arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, hasPractice: hasPractice),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -2411,8 +2460,12 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnPracticeView({ Future<dynamic> replaceWithLearnPracticeView({
_i37.Key? key, _i37.Key? key,
String? level,
required int id, required int id,
required String label,
required String title,
required _i41.LearnPractices practice, required _i41.LearnPractices practice,
required String subtitle,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
@ -2420,8 +2473,14 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition, transition,
}) async { }) async {
return replaceWith<dynamic>(Routes.learnPracticeView, return replaceWith<dynamic>(Routes.learnPracticeView,
arguments: arguments: LearnPracticeViewArguments(
LearnPracticeViewArguments(key: key, id: id, practice: practice), key: key,
level: level,
id: id,
label: label,
title: title,
practice: practice,
subtitle: subtitle),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,

View File

@ -158,6 +158,7 @@ class DioService {
return true; return true;
} catch (e) { } catch (e) {
print('REFRESH EXCEPTION: ${e.toString()}');
await _authenticationService.logout(); await _authenticationService.logout();
await _navigationService.replaceWithLoginView(); await _navigationService.replaceWithLoginView();
return false; return false;

View File

@ -3,6 +3,4 @@ import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';
class PhoneCallerService { class PhoneCallerService {
Future<void> call(String phone) async => Future<void> call(String phone) async =>
await FlutterPhoneDirectCaller.callNumber(phone); await FlutterPhoneDirectCaller.callNumber(phone);
} }

View File

@ -1,4 +1,3 @@
const String ksHomeBottomSheetTitle = 'Build Great Apps!'; const String ksHomeBottomSheetTitle = 'Build Great Apps!';
const String ksSuggestion = const String ksSuggestion =

View File

@ -238,6 +238,12 @@ TextStyle style25P600 = const TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
TextStyle style40P900 = const TextStyle(
fontSize: 40,
color: kcPrimaryColor,
fontWeight: FontWeight.w900,
);
TextStyle style25DG600 = const TextStyle( TextStyle style25DG600 = const TextStyle(
fontSize: 25, fontSize: 25,
color: kcDarkGrey, color: kcDarkGrey,
@ -309,16 +315,14 @@ TextStyle style14LG400 = const TextStyle(
TextStyle style14MG400 = const TextStyle( TextStyle style14MG400 = const TextStyle(
color: kcMediumGrey, color: kcMediumGrey,
); );
TextStyle style14DG400 = const TextStyle(color: kcDarkGrey);
TextStyle style14DG500 = TextStyle style14DG500 =
const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500); const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500);
TextStyle style18MG500 = const TextStyle( TextStyle style18MG500 = const TextStyle(
fontSize: 18, color: kcMediumGrey, fontWeight: FontWeight.w500); fontSize: 18, color: kcMediumGrey, fontWeight: FontWeight.w500);
TextStyle style14DG400 = const TextStyle(
color: kcDarkGrey,
);
TextStyle style14DG600 = const TextStyle( TextStyle style14DG600 = const TextStyle(
color: kcDarkGrey, color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@ -53,62 +53,61 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
controller: viewModel.pageController, controller: viewModel.pageController,
itemCount: viewModel.assessmentQuestions.length, itemCount: viewModel.assessmentQuestions.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) => itemBuilder: (cotext, index) => _buildBodyScroller(viewModel),
_buildBodyScroller( viewModel),
); );
Widget _buildBodyScroller( Widget _buildBodyScroller(AssessmentViewModel viewModel) =>
AssessmentViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildBody( viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody( Widget _buildBody(AssessmentViewModel viewModel) => Column(
AssessmentViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildBodyChildren( viewModel), children: _buildBodyChildren(viewModel),
); );
List<Widget> _buildBodyChildren( List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) => [
AssessmentViewModel viewModel) =>[
verticalSpaceMedium, verticalSpaceMedium,
_buildTitleState( viewModel), _buildTitleState(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildAnswersState( viewModel), _buildAnswersState(viewModel),
_buildContinueButtonWrapper( viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildTitleState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == Widget _buildTitleState(AssessmentViewModel viewModel) =>
viewModel.assessmentQuestions.length ?Container(): _buildTitle(viewModel); viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length
Widget _buildTitle( ? Container()
AssessmentViewModel viewModel) => : _buildTitle(viewModel);
Text( Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Q${viewModel.currentQuestionIndex + 1}. ${viewModel.assessmentQuestions[viewModel.currentQuestionIndex].questionText} ', 'Q${viewModel.currentQuestionIndex + 1}. ${viewModel.assessmentQuestions[viewModel.currentQuestionIndex].questionText} ',
style: style16DG600, style: style16DG600,
); );
Widget _buildAnswersState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex == Widget _buildAnswersState(AssessmentViewModel viewModel) =>
viewModel.assessmentQuestions.length ?Container(): _buildAnswers(viewModel); viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length
? Container()
: _buildAnswers(viewModel);
Widget _buildAnswers( Widget _buildAnswers(AssessmentViewModel viewModel) => ListView.builder(
AssessmentViewModel viewModel) =>
ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?.length, itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex]
.options?.length,
itemBuilder: (context, inner) => _buildAnswer( itemBuilder: (context, inner) => _buildAnswer(
onTap: () => viewModel.setSelectedAnswer( onTap: () => viewModel.setSelectedAnswer(
question: viewModel.currentQuestionIndex + 1, question: viewModel.currentQuestionIndex + 1,
option: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner]), option: viewModel
title: .assessmentQuestions[viewModel.currentQuestionIndex]
viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner].optionText ?? .options?[inner]),
title: viewModel.assessmentQuestions[viewModel.currentQuestionIndex]
.options?[inner].optionText ??
'', '',
selected: viewModel.isSelectedAnswer( selected: viewModel.isSelectedAnswer(
question: viewModel.currentQuestionIndex + 1, question: viewModel.currentQuestionIndex + 1,
answer: viewModel answer: viewModel
.assessmentQuestions[viewModel.currentQuestionIndex ].options?[inner].optionText ?? .assessmentQuestions[viewModel.currentQuestionIndex]
.options?[inner]
.optionText ??
''), ''),
), ),
); );
@ -123,15 +122,12 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
selected: selected, selected: selected,
); );
Widget _buildContinueButtonWrapper( Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
AssessmentViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton( viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton( Widget _buildContinueButton(AssessmentViewModel viewModel) =>
AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
@ -140,13 +136,13 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
viewModel.assessmentQuestions.length - 1 viewModel.assessmentQuestions.length - 1
? 'Finish Level' ? 'Finish Level'
: 'Continue', : 'Continue',
backgroundColor: backgroundColor: viewModel.selectedAnswers
viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}') .containsKey('${viewModel.currentQuestionIndex + 1}')
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}') onTap: viewModel.selectedAnswers
.containsKey('${viewModel.currentQuestionIndex + 1}')
? () => viewModel.nextQuestion() ? () => viewModel.nextQuestion()
: null, : null,
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
@ -83,9 +84,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildSubtitle('+2519012345678'), _buildSubtitle(kPhoneSupport),
verticalSpaceSmall,
_buildSubtitle('+2519012345678'),
]; ];
Widget _buildIcon() => Widget _buildIcon() =>
@ -109,12 +108,13 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
); );
Widget _buildContinueButton(CallSupportViewModel viewModel) => Widget _buildContinueButton(CallSupportViewModel viewModel) =>
const CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Tap to Call', text: 'Tap to Call',
leadingIcon: Icons.call, leadingIcon: Icons.call,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.callSupport(),
); );
} }

View File

@ -2,11 +2,18 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../services/phone_caller_service.dart';
import '../../common/app_constants.dart';
class CallSupportViewModel extends BaseViewModel { class CallSupportViewModel extends BaseViewModel {
// Dependency injection // Dependency injection
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _phoneCallerService = locator<PhoneCallerService>();
// Call support
Future<void> callSupport() => _phoneCallerService.call(kPhoneSupport);
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
} }

View File

@ -40,14 +40,6 @@ class ForgetPasswordViewModel extends FormViewModel {
bool get length => _length; bool get length => _length;
bool _number = false;
bool get number => _number;
bool _specialChar = false;
bool get specialChar => _specialChar;
bool _focusPassword = false; bool _focusPassword = false;
bool get focusPassword => _focusPassword; bool get focusPassword => _focusPassword;
@ -105,11 +97,9 @@ class ForgetPasswordViewModel extends FormViewModel {
int completed = 0; int completed = 0;
if (_length) completed++; if (_length) completed++;
if (_number) completed++;
if (_specialChar) completed++;
if (_passwordMatch) completed++; if (_passwordMatch) completed++;
return completed / 4; // returns 0.0 1.0 return completed / 2; // returns 0.0 1.0
} }
void validatePassword( void validatePassword(
@ -120,17 +110,7 @@ class ForgetPasswordViewModel extends FormViewModel {
_length = false; _length = false;
} }
if (RegExp(r'\d').hasMatch(password)) {
_number = true;
} else {
_number = false;
}
if (RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password)) {
_specialChar = true;
} else {
_specialChar = false;
}
if (password == confirmPassword) { if (password == confirmPassword) {
_passwordMatch = true; _passwordMatch = true;
@ -156,8 +136,6 @@ class ForgetPasswordViewModel extends FormViewModel {
// Reset reset password screen // Reset reset password screen
void resetResetPasswordScreen() { void resetResetPasswordScreen() {
_length = false; _length = false;
_number = false;
_specialChar = false;
_passwordMatch = false; _passwordMatch = false;
_focusPassword = false; _focusPassword = false;
_focusResetCode = false; _focusResetCode = false;

View File

@ -140,8 +140,6 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
_buildLinearProgressIndicator(viewModel), _buildLinearProgressIndicator(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildCharLengthValidator(viewModel), _buildCharLengthValidator(viewModel),
_buildNumberValidator(viewModel),
_buildSymbolValidator(viewModel),
_buildPasswordMatchValidator(viewModel), _buildPasswordMatchValidator(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSignUpButton(viewModel), _buildSignUpButton(viewModel),
@ -256,16 +254,6 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey, backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey,
label: '8 characters minimum'); label: '8 characters minimum');
Widget _buildNumberValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile(
backgroundColor: viewModel.number ? kcPrimaryColor : kcLightGrey,
label: 'a number');
Widget _buildSymbolValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile(
backgroundColor: viewModel.specialChar ? kcPrimaryColor : kcLightGrey,
label: 'one symbol minimum');
Widget _buildPasswordMatchValidator(ForgetPasswordViewModel viewModel) => Widget _buildPasswordMatchValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile( ValidatorListTile(
backgroundColor: backgroundColor:
@ -281,20 +269,14 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
onTap: passwordController.text.isNotEmpty && onTap: passwordController.text.isNotEmpty &&
confirmPasswordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty &&
resetCodeController.text.isNotEmpty && resetCodeController.text.isNotEmpty &&
viewModel.number &&
viewModel.length && viewModel.length &&
viewModel.specialChar &&
viewModel.specialChar &&
viewModel.passwordMatch viewModel.passwordMatch
? () async => await _reset(viewModel) ? () async => await _reset(viewModel)
: null, : null,
backgroundColor: passwordController.text.isNotEmpty && backgroundColor: passwordController.text.isNotEmpty &&
confirmPasswordController.text.isNotEmpty && confirmPasswordController.text.isNotEmpty &&
resetCodeController.text.isNotEmpty && resetCodeController.text.isNotEmpty &&
viewModel.number &&
viewModel.length && viewModel.length &&
viewModel.specialChar &&
viewModel.specialChar &&
viewModel.passwordMatch viewModel.passwordMatch
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),

View File

@ -84,8 +84,9 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
course: viewModel.learnCourses[index], course: viewModel.learnCourses[index],
onViewTap: () async => await viewModel onViewTap: () async => await viewModel
.navigateToLearnModule(viewModel.learnCourses[index]), .navigateToLearnModule(viewModel.learnCourses[index]),
onPracticeTap: () async => await viewModel onPracticeTap: () async => await viewModel.navigateToLearnPractice(
.navigateToLearnPractice(viewModel.learnCourses[index].id ?? 0), id: viewModel.learnCourses[index].id ?? 0,
level: viewModel.learnCourses[index].name ?? ''),
), ),
separatorBuilder: (context, index) => verticalSpaceSmall, separatorBuilder: (context, index) => verticalSpaceSmall,
); );

View File

@ -27,8 +27,16 @@ class LearnCourseViewModel extends BaseViewModel {
Future<void> navigateToLearnModule(LearnCourse course) async => Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course); _navigationService.navigateToLearnModuleView(course: course);
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(
.navigateToLearnPracticeView(id: id, practice: LearnPractices.course); {required int id, required String level}) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
level: level,
label: 'Begin Level Practice',
practice: LearnPractices.course,
title: 'Lets Practice Course $level',
subtitle: 'Lets quickly review what youve learned in this level!',
);
// Remote api call // Remote api call

View File

@ -115,9 +115,9 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
_buildSubtitle(), _buildSubtitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildModuleProgress(), _buildModuleProgress(),
verticalSpaceMedium, verticalSpaceLarge,
_buildMotivationCard(), _buildMotivationCard(),
verticalSpaceMedium, verticalSpaceLarge,
_buildHeader(), _buildHeader(),
verticalSpaceMedium, verticalSpaceMedium,
_buildListViewBuilder(viewModel), _buildListViewBuilder(viewModel),
@ -156,20 +156,25 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
itemCount: viewModel.lessons.length, itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
index: index,
lesson: viewModel.lessons[index], lesson: viewModel.lessons[index],
onLessonTap: () async => await viewModel onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
.navigateToLearnLessonDetail(viewModel.lessons[index]), lesson: viewModel.lessons[index],
hasPractice:
index != viewModel.lessons.length - 1 ? true : false),
onPracticeTap: () async => await viewModel onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.lessons[index].id ?? 0), .navigateToLearnPractice(viewModel.lessons[index].id ?? 0),
), ),
); );
Widget _buildTile({ Widget _buildTile({
required int index,
required LearnLesson lesson, required LearnLesson lesson,
required GestureTapCallback? onLessonTap, required GestureTapCallback? onLessonTap,
required GestureTapCallback? onPracticeTap, required GestureTapCallback? onPracticeTap,
}) => }) =>
LearnLessonTile( LearnLessonTile(
index: index,
lesson: lesson, lesson: lesson,
onLessonTap: onLessonTap, onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap, onPracticeTap: onPracticeTap,

View File

@ -24,11 +24,20 @@ class LearnLessonViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(int id) async =>
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Start Practice',
practice: LearnPractices.lesson,
title: 'Let\'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
Future<void> navigateToLearnLessonDetail(LearnLesson lesson) async => Future<void> navigateToLearnLessonDetail(
await _navigationService.navigateToLearnLessonDetailView(lesson: lesson); {required bool hasPractice, required LearnLesson lesson}) async =>
await _navigationService.navigateToLearnLessonDetailView(
lesson: lesson, hasPractice: hasPractice);
// Remote api call // Remote api call

View File

@ -13,9 +13,11 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_detail_viewmodel.dart'; import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> { class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
final bool hasPractice;
final LearnLesson lesson; final LearnLesson lesson;
const LearnLessonDetailView({Key? key, required this.lesson}) const LearnLessonDetailView(
{Key? key, required this.lesson, required this.hasPractice})
: super(key: key); : super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async { Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
@ -86,7 +88,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
List<Widget> _buildBodyColumnChildren(LearnLessonDetailViewModel viewModel) => List<Widget> _buildBodyColumnChildren(LearnLessonDetailViewModel viewModel) =>
[ [
_buildLevelsColumnWrapper(viewModel), _buildLevelsColumnWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) if (hasPractice) _buildPracticeButtonWrapper(viewModel)
]; ];
Widget _buildLevelsColumnWrapper(LearnLessonDetailViewModel viewModel) => Widget _buildLevelsColumnWrapper(LearnLessonDetailViewModel viewModel) =>
@ -157,21 +159,21 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
style: style14DG400, style: style14DG400,
); );
Widget _buildContinueButtonWrapper(LearnLessonDetailViewModel viewModel) => Widget _buildPracticeButtonWrapper(LearnLessonDetailViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 15, left: 15,
right: 15, right: 15,
bottom: 50, bottom: 50,
), ),
child: _buildContinueButton(viewModel), child: _buildPracticeButton(viewModel),
); );
Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) => Widget _buildPracticeButton(LearnLessonDetailViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Practices', text: 'Take Practice',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel), onTap: () async => await _navigate(viewModel),

View File

@ -67,6 +67,13 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(int id) async =>
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Start Practice',
practice: LearnPractices.lesson,
title: 'Let\'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
} }

View File

@ -116,8 +116,9 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index], module: viewModel.modules[index],
onPracticeTap: () async => await viewModel onPracticeTap: () async => await viewModel.navigateToLearnPractice(
.navigateToLearnPractice(viewModel.modules[index].id ?? 0), id: viewModel.modules[index].id ?? 0,
module: viewModel.modules[index].name ?? ''),
onModuleTap: () async => onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]), await viewModel.navigateToLearnLesson(viewModel.modules[index]),
), ),

View File

@ -27,8 +27,15 @@ class LearnModuleViewModel extends BaseViewModel {
Future<void> navigateToLearnLesson(LearnModule module) async => Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module); await _navigationService.navigateToLearnLessonView(module: module);
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(
.navigateToLearnPracticeView(id: id, practice: LearnPractices.module); {required int id, required String module}) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Begin Module Practice',
practice: LearnPractices.module,
title: 'Lets Practice $module',
subtitle: 'Lets quickly review what youve learned in this module! ',
);
// Remote api call // Remote api call

View File

@ -16,10 +16,21 @@ import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> { class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
final int id; final int id;
final String label;
final String title;
final String? level;
final String subtitle;
final LearnPractices practice; final LearnPractices practice;
const LearnPracticeView({Key? key, required this.id, required this.practice}) const LearnPracticeView({
: super(key: key); Key? key,
this.level,
required this.id,
required this.label,
required this.title,
required this.practice,
required this.subtitle,
}) : super(key: key);
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording(); await viewModel.stopRecording();
@ -108,10 +119,17 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
_buildLearnPracticeQuestionsScreen(), _buildLearnPracticeQuestionsScreen(),
_buildFinishLearnPracticeScreen(), _buildFinishLearnPracticeScreen(),
_buildLearnPracticeResultScreen(), _buildLearnPracticeResultScreen(),
if (practice == LearnPractices.course)
_buildLearnPracticeCompletionScreen() _buildLearnPracticeCompletionScreen()
]; ];
Widget _buildLearnPracticeIntroScreen() => const LearnPracticeIntroScreen(); Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen(
level: level,
title: title,
label: label,
practice: practice,
subtitle: subtitle,
);
Widget _buildLearnPracticeElementsScreen() => Widget _buildLearnPracticeElementsScreen() =>
const LearnPracticeDescriptionScreen(); const LearnPracticeDescriptionScreen();
@ -121,8 +139,9 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen(); Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen();
Widget _buildLearnPracticeResultScreen() => const LearnPracticeResultScreen(); Widget _buildLearnPracticeResultScreen() =>
LearnPracticeResultScreen(practice: practice);
Widget _buildLearnPracticeCompletionScreen() => Widget _buildLearnPracticeCompletionScreen() =>
const LearnPracticeCompletionScreen(); LearnPracticeCompletionScreen(level: level ?? '');
} }

View File

@ -225,7 +225,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
await stopRecording(); await stopRecording();
_answers.add({ _answers.add({
'busy_object': question.id.toString(), 'busy_object': question.id.toString(),
'sample_text_answer': question.audioCorrectAnswerText, 'question_text': question.questionText,
'sample_voice_answer': question.sampleAnswerVoicePrompt, 'sample_voice_answer': question.sampleAnswerVoicePrompt,
'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(), 'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(),
}); });

View File

@ -9,7 +9,8 @@ import '../../../widgets/custom_elevated_button.dart';
class LearnPracticeCompletionScreen class LearnPracticeCompletionScreen
extends ViewModelWidget<LearnPracticeViewModel> { extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeCompletionScreen({super.key}); final String level;
const LearnPracticeCompletionScreen({super.key, required this.level});
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
@ -54,7 +55,7 @@ class LearnPracticeCompletionScreen
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Yay, youve completed A1 ', 'Yay, youve completed $level ',
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );

View File

@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
@ -10,7 +11,19 @@ import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart'; import '../../../widgets/speaking_partner_image.dart';
class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> { class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeIntroScreen({super.key}); final String title;
final String label;
final String? level;
final String subtitle;
final LearnPractices practice;
const LearnPracticeIntroScreen(
{super.key,
this.level,
required this.label,
required this.title,
required this.subtitle,
required this.practice});
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording(); await viewModel.stopRecording();
@ -114,21 +127,44 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) =>
[ [
verticalSpaceMassive, verticalSpaceMassive,
_buildImage(), _buildImageState(),
verticalSpaceMedium, verticalSpaceMedium,
_buildPartnerName(), _buildPartnerNameState(),
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildSubtitle() _buildSubtitle()
]; ];
Widget _buildImage() => const SpeakingPartnerImage( Widget _buildImageState() =>
level != null ? _buildCourseSpeakingPartnerImage() : _buildImage(75);
Widget _buildCourseSpeakingPartnerImage() => CircleAvatar(
radius: 75, radius: 75,
backgroundColor: kcPrimaryColorLight,
child: _buildCourseSpeakingPartnerText(),
); );
Widget _buildCourseSpeakingPartnerText() => Text(
level?.toUpperCase() ?? '',
style: style40P900,
);
Widget _buildImage(double radius) => SpeakingPartnerImage(radius: radius);
Widget _buildPartnerNameState() =>
level != null ? _buildCourseSpeakingPartner() : _buildPartnerName();
Widget _buildCourseSpeakingPartner() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildCourseSpeakingPartnerChildren(),
);
List<Widget> _buildCourseSpeakingPartnerChildren() =>
[_buildImage(15), horizontalSpaceTiny, _buildPartnerName()];
Widget _buildPartnerName() => Text.rich( Widget _buildPartnerName() => Text.rich(
TextSpan(text: 'Dawit', style: style14DG600, children: [ TextSpan(text: 'Daniel', style: style14DG600, children: [
TextSpan( TextSpan(
text: ' - Your Speaking Partner', text: ' - Your Speaking Partner',
style: style14MG400, style: style14MG400,
@ -137,13 +173,13 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Let\'s practice what you just learnt!', title,
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Ill ask you a few questions, and you can respond naturally.', subtitle,
maxLines: 1, maxLines: 1,
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -158,8 +194,8 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildContinueButton(LearnPracticeViewModel viewModel) => Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: label,
borderRadius: 12, borderRadius: 12,
text: 'Practice',
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(1), onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart';
@ -12,9 +13,19 @@ import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> { extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultScreen({super.key}); final LearnPractices practice;
void _navigate(LearnPracticeViewModel viewModel) async => const LearnPracticeResultScreen({super.key, required this.practice});
Future<void> _navigate(LearnPracticeViewModel viewModel) async {
if (practice == LearnPractices.course) {
viewModel.goTo(5);
} else {
viewModel.pop();
}
}
Future<void> _retry(LearnPracticeViewModel viewModel) async =>
await viewModel.reset(); await viewModel.reset();
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
@ -145,8 +156,8 @@ class LearnPracticeResultScreen
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(5),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel),
); );
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) => Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>
@ -157,6 +168,6 @@ class LearnPracticeResultScreen
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => _navigate(viewModel), onTap: () async => await _retry(viewModel),
); );
} }

View File

@ -26,10 +26,6 @@ class ProfileViewModel extends ReactiveViewModel {
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _phoneCallerService = locator<PhoneCallerService>();
final _urlLauncherService = locator<UrlLauncherService>();
final _imagePickerService = locator<ImagePickerService>(); final _imagePickerService = locator<ImagePickerService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@ -68,13 +64,6 @@ class ProfileViewModel extends ReactiveViewModel {
} }
} }
// Launch telegram
Future<void> launchTelegram() =>
_urlLauncherService.launchUri(kTelegramSupport);
// Call support
Future<void> callSupport() => _phoneCallerService.call(kPhoneSupport);
// Dialog // Dialog
Future<bool?> showAbortDialog() async { Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog( DialogResponse? response = await _dialogService.showDialog(

View File

@ -66,13 +66,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
content: _buildImagePicker(context: context, viewModel: viewModel), content: _buildImagePicker(context: context, viewModel: viewModel),
); );
void _checkRegion(ProfileDetailViewModel viewModel){ void _checkRegion(ProfileDetailViewModel viewModel) {
bool region = viewModel.checkRegion(region:viewModel.user?.region ?? 'Addis Ababa',country:viewModel.user?.country ?? 'Ethiopia' ); bool region = viewModel.checkRegion(
if(region){ region: viewModel.user?.region ?? 'Addis Ababa',
country: viewModel.user?.country ?? 'Ethiopia');
if (region) {
viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa'); viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa');
}else{ } else {
regionController.text = viewModel.user?.region ?? ''; regionController.text = viewModel.user?.region ?? '';
} }
} }
@ -549,10 +550,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildRegionFormState(viewModel), _buildRegionFormState(viewModel),
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&
viewModel.focusRegion) verticalSpaceTiny, viewModel.focusRegion)
verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&
viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel), viewModel.focusRegion)
_buildRegionValidatorWrapper(viewModel),
]; ];
Widget _buildRegionFormFieldLabel() => CustomFormLabel( Widget _buildRegionFormFieldLabel() => CustomFormLabel(
@ -574,7 +577,8 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onChanged: (value) => onChanged: (value) =>
viewModel.setSelectedRegion(value ?? 'Addis Ababa')); viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => TextFormField( Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
TextFormField(
controller: regionController, controller: regionController,
onTap: viewModel.setRegionFocus, onTap: viewModel.setRegionFocus,
decoration: inputDecoration( decoration: inputDecoration(
@ -593,7 +597,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
style: style12R700, style: style12R700,
); );
Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) => Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) =>
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -608,7 +611,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildOccupationDropdownLabel(), _buildOccupationDropdownLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildOccupationDropdown(viewModel) _buildOccupationDropdown(viewModel)
]; ];
Widget _buildOccupationDropdownLabel() => CustomFormLabel( Widget _buildOccupationDropdownLabel() => CustomFormLabel(

View File

@ -18,7 +18,6 @@ const String RegionValueKey = 'region';
const String PhoneNumberValueKey = 'phoneNumber'; const String PhoneNumberValueKey = 'phoneNumber';
const String LastNameValueKey = 'lastName'; const String LastNameValueKey = 'lastName';
const String FirstNameValueKey = 'firstName'; const String FirstNameValueKey = 'firstName';
const String OccupationValueKey = 'occupation';
final Map<String, TextEditingController> final Map<String, TextEditingController>
_ProfileDetailViewTextEditingControllers = {}; _ProfileDetailViewTextEditingControllers = {};
@ -32,7 +31,6 @@ final Map<String, String? Function(String?)?>
PhoneNumberValueKey: FormValidator.validatePhoneNumberForm, PhoneNumberValueKey: FormValidator.validatePhoneNumberForm,
LastNameValueKey: FormValidator.validateForm, LastNameValueKey: FormValidator.validateForm,
FirstNameValueKey: FormValidator.validateForm, FirstNameValueKey: FormValidator.validateForm,
OccupationValueKey: FormValidator.validateForm,
}; };
mixin $ProfileDetailView { mixin $ProfileDetailView {
@ -46,15 +44,12 @@ mixin $ProfileDetailView {
_getFormTextEditingController(LastNameValueKey); _getFormTextEditingController(LastNameValueKey);
TextEditingController get firstNameController => TextEditingController get firstNameController =>
_getFormTextEditingController(FirstNameValueKey); _getFormTextEditingController(FirstNameValueKey);
TextEditingController get occupationController =>
_getFormTextEditingController(OccupationValueKey);
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey); FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey); FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey);
FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey); FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey);
FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey); FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey);
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
TextEditingController _getFormTextEditingController( TextEditingController _getFormTextEditingController(
String key, { String key, {
@ -85,7 +80,6 @@ mixin $ProfileDetailView {
phoneNumberController.addListener(() => _updateFormData(model)); phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -102,7 +96,6 @@ mixin $ProfileDetailView {
phoneNumberController.addListener(() => _updateFormData(model)); phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model)); lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model)); firstNameController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -117,7 +110,6 @@ mixin $ProfileDetailView {
PhoneNumberValueKey: phoneNumberController.text, PhoneNumberValueKey: phoneNumberController.text,
LastNameValueKey: lastNameController.text, LastNameValueKey: lastNameController.text,
FirstNameValueKey: firstNameController.text, FirstNameValueKey: firstNameController.text,
OccupationValueKey: occupationController.text,
}), }),
); );
@ -165,8 +157,6 @@ extension ValueProperties on FormStateHelper {
this.formValueMap[PhoneNumberValueKey] as String?; this.formValueMap[PhoneNumberValueKey] as String?;
String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?; String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?;
String? get firstNameValue => this.formValueMap[FirstNameValueKey] as String?; String? get firstNameValue => this.formValueMap[FirstNameValueKey] as String?;
String? get occupationValue =>
this.formValueMap[OccupationValueKey] as String?;
set emailValue(String? value) { set emailValue(String? value) {
this.setData( this.setData(
@ -226,18 +216,6 @@ extension ValueProperties on FormStateHelper {
} }
} }
set occupationValue(String? value) {
this.setData(
this.formValueMap..addAll({OccupationValueKey: value}),
);
if (_ProfileDetailViewTextEditingControllers.containsKey(
OccupationValueKey)) {
_ProfileDetailViewTextEditingControllers[OccupationValueKey]?.text =
value ?? '';
}
}
bool get hasEmail => bool get hasEmail =>
this.formValueMap.containsKey(EmailValueKey) && this.formValueMap.containsKey(EmailValueKey) &&
(emailValue?.isNotEmpty ?? false); (emailValue?.isNotEmpty ?? false);
@ -253,9 +231,6 @@ extension ValueProperties on FormStateHelper {
bool get hasFirstName => bool get hasFirstName =>
this.formValueMap.containsKey(FirstNameValueKey) && this.formValueMap.containsKey(FirstNameValueKey) &&
(firstNameValue?.isNotEmpty ?? false); (firstNameValue?.isNotEmpty ?? false);
bool get hasOccupation =>
this.formValueMap.containsKey(OccupationValueKey) &&
(occupationValue?.isNotEmpty ?? false);
bool get hasEmailValidationMessage => bool get hasEmailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
@ -267,8 +242,6 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[LastNameValueKey]?.isNotEmpty ?? false;
bool get hasFirstNameValidationMessage => bool get hasFirstNameValidationMessage =>
this.fieldsValidationMessages[FirstNameValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[FirstNameValueKey]?.isNotEmpty ?? false;
bool get hasOccupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false;
String? get emailValidationMessage => String? get emailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey]; this.fieldsValidationMessages[EmailValueKey];
@ -280,8 +253,6 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey]; this.fieldsValidationMessages[LastNameValueKey];
String? get firstNameValidationMessage => String? get firstNameValidationMessage =>
this.fieldsValidationMessages[FirstNameValueKey]; this.fieldsValidationMessages[FirstNameValueKey];
String? get occupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey];
} }
extension Methods on FormStateHelper { extension Methods on FormStateHelper {
@ -295,8 +266,6 @@ extension Methods on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey] = validationMessage; this.fieldsValidationMessages[LastNameValueKey] = validationMessage;
void setFirstNameValidationMessage(String? validationMessage) => void setFirstNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FirstNameValueKey] = validationMessage; this.fieldsValidationMessages[FirstNameValueKey] = validationMessage;
void setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
/// Clears text input fields on the Form /// Clears text input fields on the Form
void clearForm() { void clearForm() {
@ -305,7 +274,6 @@ extension Methods on FormStateHelper {
phoneNumberValue = ''; phoneNumberValue = '';
lastNameValue = ''; lastNameValue = '';
firstNameValue = ''; firstNameValue = '';
occupationValue = '';
} }
/// Validates text input fields on the Form /// Validates text input fields on the Form
@ -316,7 +284,6 @@ extension Methods on FormStateHelper {
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
}); });
} }
} }
@ -341,5 +308,4 @@ void updateValidationData(FormStateHelper model) =>
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey), PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey), LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey), FirstNameValueKey: getValidationMessage(FirstNameValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
}); });

View File

@ -57,13 +57,11 @@ class ProfileDetailViewModel extends ReactiveViewModel
bool get focusEmail => _focusEmail; bool get focusEmail => _focusEmail;
// Occupation // Occupation
String _selectedOccupation = 'Students (High school & University)'; String _selectedOccupation = 'Students (High school & University)';
String get selectedOccupation => _selectedOccupation; String get selectedOccupation => _selectedOccupation;
// Country // Country
String _selectedCountry = 'Ethiopia'; String _selectedCountry = 'Ethiopia';
@ -117,8 +115,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Occupation // Occupation
List<String> getOccupations() => [ List<String> getOccupations() => [
'Students (High school & University)', 'Students (High school & University)',
@ -321,8 +317,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
'Tigray', 'Tigray',
]; ];
bool checkRegion({required String region,required String country}){ bool checkRegion({required String region, required String country}) {
if(country == 'Ethiopia'){ if (country == 'Ethiopia') {
return getRegions().contains(region); return getRegions().contains(region);
} }
return false; return false;

View File

@ -130,7 +130,6 @@ class RegisterViewModel extends ReactiveViewModel
_length = false; _length = false;
} }
if (password == confirmPassword) { if (password == confirmPassword) {
_passwordMatch = true; _passwordMatch = true;
} else { } else {
@ -237,6 +236,7 @@ class RegisterViewModel extends ReactiveViewModel
} }
void goBack() { void goBack() {
print('HERE');
if (_currentPage == 1) { if (_currentPage == 1) {
_currentPage = 0; _currentPage = 0;
rebuildUi(); rebuildUi();

View File

@ -213,7 +213,6 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey, backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey,
label: '8 characters minimum'); label: '8 characters minimum');
Widget _buildPasswordMatchValidator(RegisterViewModel viewModel) => Widget _buildPasswordMatchValidator(RegisterViewModel viewModel) =>
ValidatorListTile( ValidatorListTile(
backgroundColor: backgroundColor:

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import 'package:yimaru_app/ui/widgets/circular_icon.dart'; import 'package:yimaru_app/ui/widgets/circular_icon.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
@ -91,20 +92,16 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
Widget _buildIcon() => Widget _buildIcon() =>
const CircularIcon(icon: Icons.telegram, size: 50, color: kcSkyBlue); const CircularIcon(icon: Icons.telegram, size: 50, color: kcSkyBlue);
Widget _buildTitle() => const Text( Widget _buildTitle() => Text(
'Join Yimaru Academy on Telegram', 'Join Yimaru Academy on Telegram',
style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
); );
Widget _buildSubtitle() => const Text( Widget _buildSubtitle() => Text(
'Connect with our support team instantly on Telegram for quick assistance and community updates', 'Connect with our support team instantly on Telegram for quick assistance and community updates',
style: style14MG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: kcMediumGrey),
); );
Widget _buildLowerColumn(TelegramSupportViewModel viewModel) => Column( Widget _buildLowerColumn(TelegramSupportViewModel viewModel) => Column(
@ -123,30 +120,23 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
]; ];
Widget _buildContinueButton(TelegramSupportViewModel viewModel) => Widget _buildContinueButton(TelegramSupportViewModel viewModel) =>
const CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
leadingIcon: Icons.telegram,
text: 'Open in Telegram', text: 'Open in Telegram',
foregroundColor: kcWhite, foregroundColor: kcWhite,
leadingIcon: Icons.telegram,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.launchTelegram(),
); );
Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildSearchText() => const Text.rich( Widget _buildSearchText() => Text.rich(
TextSpan(text: 'Search for', style: style14DG500, children: [
TextSpan( TextSpan(
text: 'Search for', style: style14P600,
style: TextStyle( text: ' $kTelegramSupport',
color: kcDarkGrey,
),
children: [
TextSpan(
text: ' @YimaruSupport',
style: TextStyle(
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
),
) )
]), ]),
); );

View File

@ -2,8 +2,19 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../services/url_launcher_service.dart';
import '../../common/app_constants.dart';
class TelegramSupportViewModel extends BaseViewModel { class TelegramSupportViewModel extends BaseViewModel {
// Dependency injection
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _urlLauncherService = locator<UrlLauncherService>();
// Launch telegram
Future<void> launchTelegram() =>
_urlLauncherService.launchUri(kTelegramSupport);
// Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
} }

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
class AppBarPattern extends StatelessWidget {
const AppBarPattern({
super.key,
});
@override
Widget build(BuildContext context) => _buildDecorationImageWrapper();
Widget _buildDecorationImageWrapper() => ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
child: _buildDecorationImage(),
);
Widget _buildDecorationImage() => SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: _buildPatternWrapper(),
);
Widget _buildPatternWrapper() => SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: _buildPatternMask(),
);
Widget _buildPatternMask() => ShaderMask(
shaderCallback: (Rect bounds) => const LinearGradient(
colors: [kcWhite, kcWhite],
).createShader(bounds),
blendMode: BlendMode.modulate,
child: _buildPattern(),
);
Widget _buildPattern() => Image.asset(
'assets/images/pattern.png',
fit: BoxFit.cover,
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart';
import 'package:yimaru_app/ui/widgets/language_button.dart'; import 'package:yimaru_app/ui/widgets/language_button.dart';
class LargeAppBar extends StatelessWidget { class LargeAppBar extends StatelessWidget {
@ -18,12 +19,11 @@ class LargeAppBar extends StatelessWidget {
required this.showLanguageSelection}); required this.showLanguageSelection});
@override @override
Widget build(BuildContext context) => _buildAppBarWrapper(); Widget build(BuildContext context) => _buildStackWrapper();
Widget _buildAppBarWrapper() => Container( Widget _buildStackWrapper() => Container(
height: 125, height: 125,
width: double.maxFinite, width: double.maxFinite,
alignment: Alignment.bottomCenter,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: kcPrimaryColor, color: kcPrimaryColor,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@ -31,6 +31,18 @@ class LargeAppBar extends StatelessWidget {
bottomRight: Radius.circular(24), bottomRight: Radius.circular(24),
), ),
), ),
child: _buildStack(),
);
Widget _buildStack() => Stack(
children: [ _buildPattern(),_buildAppBarWrapper()],
);
Widget _buildAppBarWrapper() => Container(
color: kcTransparent,
width: double.maxFinite,
height: double.maxFinite,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 25, right: 15), padding: const EdgeInsets.only(bottom: 25, right: 15),
child: _buildAppBarItems(), child: _buildAppBarItems(),
); );
@ -74,4 +86,6 @@ class LargeAppBar extends StatelessWidget {
Icons.close, Icons.close,
color: kcWhite, color: kcWhite,
); );
Widget _buildPattern() => const AppBarPattern();
} }

View File

@ -143,7 +143,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
height: 15, height: 15,
borderRadius: 12, borderRadius: 12,
onTap: onViewTap, onTap: onViewTap,
text: 'View Courses', text: 'View Course',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
@ -158,7 +158,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
height: 15, height: 15,
borderRadius: 12, borderRadius: 12,
onTap: onPracticeTap, onTap: onPracticeTap,
text: 'Take Practices', text: 'Take Practice',
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,

View File

@ -12,12 +12,16 @@ import 'custom_elevated_button.dart';
import 'custom_linear_progress_indicator.dart'; import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> { class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
final int index;
final LearnLesson lesson; final LearnLesson lesson;
final GestureTapCallback? onLessonTap; final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap; final GestureTapCallback? onPracticeTap;
const LearnLessonTile( const LearnLessonTile({super.key,
{super.key, this.onLessonTap, this.onPracticeTap, required this.lesson}); this.onLessonTap,
this.onPracticeTap,
required this.index,
required this.lesson});
@override @override
Widget build(BuildContext context, LearnLessonViewModel viewModel) => Widget build(BuildContext context, LearnLessonViewModel viewModel) =>
@ -52,8 +56,8 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
expandedAlignment: Alignment.centerLeft, expandedAlignment: Alignment.centerLeft,
backgroundColor: kcPrimaryColor.withOpacity(0.1), backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 0),
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1), collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15), childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
// enabled: (lesson.access?.isAccessible ?? false), // enabled: (lesson.access?.isAccessible ?? false),
@ -130,10 +134,11 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildActionButtons(LearnLessonViewModel viewModel) => Row( Widget _buildActionButtons(LearnLessonViewModel viewModel) => Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: __buildActionButtonChildren(viewModel), children: _buildActionButtonChildren(viewModel),
); );
List<Widget> __buildActionButtonChildren(LearnLessonViewModel viewModel) => [ List<Widget> _buildActionButtonChildren(LearnLessonViewModel viewModel) => [
if (index != viewModel.lessons.length - 1)
_buildPracticeButtonWrapper(viewModel), _buildPracticeButtonWrapper(viewModel),
horizontalSpaceSmall, horizontalSpaceSmall,
_buildLessonButtonWrapper(viewModel) _buildLessonButtonWrapper(viewModel)

View File

@ -15,7 +15,7 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
_buildColumnWrapper(viewModel); _buildColumnWrapper(viewModel);
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => SizedBox( Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => SizedBox(
height: 100, height: 125,
width: double.maxFinite, width: double.maxFinite,
child: _buildColumn(viewModel), child: _buildColumn(viewModel),
); );
@ -30,7 +30,8 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
[_buildQuestion(viewModel), verticalSpaceSmall, _buildRow()]; [_buildQuestion(viewModel), verticalSpaceSmall, _buildRow()];
Widget _buildQuestion(LearnPracticeViewModel viewModel) => Text( Widget _buildQuestion(LearnPracticeViewModel viewModel) => Text(
answer['sample_text_answer'], answer['question_text'],
maxLines: 2,
style: style14DG400, style: style14DG400,
); );

View File

@ -14,8 +14,7 @@ class MiniThumbnail extends StatelessWidget {
Widget build(BuildContext context) => _buildWrapper(); Widget build(BuildContext context) => _buildWrapper();
Widget _buildWrapper() => SizedBox( Widget _buildWrapper() => SizedBox(
width: 75, width: 80,
height: double.maxFinite,
child: _buildLeadingClipper(), child: _buildLeadingClipper(),
); );
@ -40,15 +39,17 @@ class MiniThumbnail extends StatelessWidget {
: _buildNetworkImage(); : _buildNetworkImage();
Widget _buildNetworkImage() => CachedNetworkImage( Widget _buildNetworkImage() => CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: thumbnail, imageUrl: thumbnail,
fit: BoxFit.fill,
width: double.maxFinite, width: double.maxFinite,
height: double.maxFinite,
); );
Widget _buildLocalImage() => Image.asset( Widget _buildLocalImage() => Image.asset(
thumbnail, thumbnail,
fit: BoxFit.fill, fit: BoxFit.cover,
width: double.maxFinite, width: double.maxFinite,
height: double.maxFinite,
); );
Widget _buildPlayButtonWrapper() => Align( Widget _buildPlayButtonWrapper() => Align(

View File

@ -1,5 +1,5 @@
name: yimaru_app name: yimaru_app
version: 0.1.13+15 version: 0.1.14+16
publish_to: 'none' publish_to: 'none'
description: A new Flutter project. description: A new Flutter project.