fix(learn): Fix lesson lock logic issue

This commit is contained in:
BisratHailu 2026-05-29 16:14:31 +03:00
parent 8be9516338
commit b6872e2a3f
43 changed files with 705 additions and 660 deletions

View File

@ -501,6 +501,7 @@ class StackedRouter extends _i1.RouterBase {
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i21.LearnLessonDetailView(
key: args.key,
index: args.index,
lesson: args.lesson,
module: args.module,
hasPractice: args.hasPractice),
@ -1083,6 +1084,7 @@ class ForgetPasswordViewArguments {
class LearnLessonDetailViewArguments {
const LearnLessonDetailViewArguments({
this.key,
required this.index,
required this.lesson,
required this.module,
required this.hasPractice,
@ -1090,6 +1092,8 @@ class LearnLessonDetailViewArguments {
final _i37.Key? key;
final int index;
final _i40.LearnLesson lesson;
final _i39.LearnModule module;
@ -1098,13 +1102,14 @@ class LearnLessonDetailViewArguments {
@override
String toString() {
return '{"key": "$key", "lesson": "$lesson", "module": "$module", "hasPractice": "$hasPractice"}';
return '{"key": "$key", "index": "$index", "lesson": "$lesson", "module": "$module", "hasPractice": "$hasPractice"}';
}
@override
bool operator ==(covariant LearnLessonDetailViewArguments other) {
if (identical(this, other)) return true;
return other.key == key &&
other.index == index &&
other.lesson == lesson &&
other.module == module &&
other.hasPractice == hasPractice;
@ -1113,6 +1118,7 @@ class LearnLessonDetailViewArguments {
@override
int get hashCode {
return key.hashCode ^
index.hashCode ^
lesson.hashCode ^
module.hashCode ^
hasPractice.hashCode;
@ -1839,6 +1845,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnLessonDetailView({
_i37.Key? key,
required int index,
required _i40.LearnLesson lesson,
required _i39.LearnModule module,
required bool hasPractice,
@ -1850,7 +1857,11 @@ extension NavigatorStateExtension on _i46.NavigationService {
}) async {
return navigateTo<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, module: module, hasPractice: hasPractice),
key: key,
index: index,
lesson: lesson,
module: module,
hasPractice: hasPractice),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -2430,6 +2441,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnLessonDetailView({
_i37.Key? key,
required int index,
required _i40.LearnLesson lesson,
required _i39.LearnModule module,
required bool hasPractice,
@ -2441,7 +2453,11 @@ extension NavigatorStateExtension on _i46.NavigationService {
}) async {
return replaceWith<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, module: module, hasPractice: hasPractice),
key: key,
index: index,
lesson: lesson,
module: module,
hasPractice: hasPractice),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,

View File

@ -1,6 +1,7 @@
import 'package:json_annotation/json_annotation.dart';
part 'access.g.dart';
@JsonSerializable()
class Access {
final String? reason;
@ -51,12 +52,10 @@ class Access {
progressPercentPrecise ?? this.progressPercentPrecise,
completedCount: completedCount ?? this.completedCount,
progressPercent: progressPercent ?? this.progressPercent,
);
}
factory Access.fromJson(Map<String, dynamic> json) =>
_$AccessFromJson(json);
factory Access.fromJson(Map<String, dynamic> json) => _$AccessFromJson(json);
Map<String, dynamic> toJson() => _$AccessToJson(this);
}

View File

@ -15,16 +15,14 @@ class CourseProgress {
final List<ModuleProgress>? modules;
@JsonKey(name: 'program_id')
final int? programId;
const CourseProgress(
{this.id, this.name, this.access, this.modules, this.programId});
factory CourseProgress.fromJson(Map<String, dynamic> json) => _$CourseProgressFromJson(json);
factory CourseProgress.fromJson(Map<String, dynamic> json) =>
_$CourseProgressFromJson(json);
Map<String, dynamic> toJson() => _$CourseProgressToJson(this);
}

View File

@ -35,7 +35,6 @@ class LearnCourse {
int? sortOrder,
int? programId,
String? description,
}) {
return LearnCourse(
id: id ?? this.id,

View File

@ -3,6 +3,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'access.dart';
part 'learn_lesson.g.dart';
@JsonSerializable()
class LearnLesson {
final int? id;
@ -44,7 +45,6 @@ class LearnLesson {
String? videoUrl,
String? thumbnail,
String? description,
}) {
return LearnLesson(
id: id ?? this.id,

View File

@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/access.dart';
part 'learn_module.g.dart';
@JsonSerializable()
class LearnModule {
final int? id;
@ -43,7 +44,6 @@ class LearnModule {
int? programId,
int? sortOrder,
String? description,
}) {
return LearnModule(
id: id ?? this.id,
@ -54,7 +54,6 @@ class LearnModule {
sortOrder: sortOrder ?? this.sortOrder,
programId: programId ?? this.programId,
description: description ?? this.description,
);
}

View File

@ -2,6 +2,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/access.dart';
part 'learn_program.g.dart';
@JsonSerializable()
class LearnProgram {
final int? id;

View File

@ -15,11 +15,10 @@ class LessonProgress {
@JsonKey(name: 'module_id')
final int? moduleId;
const LessonProgress({this.id, this.title, this.access, this.moduleId});
const LessonProgress(
{this.id,this.title,this.access,this.moduleId});
factory LessonProgress.fromJson(Map<String, dynamic> json) => _$LessonProgressFromJson(json);
factory LessonProgress.fromJson(Map<String, dynamic> json) =>
_$LessonProgressFromJson(json);
Map<String, dynamic> toJson() => _$LessonProgressToJson(this);
}

View File

@ -15,10 +15,10 @@ class ProgressSummary {
final List<CourseProgress>? courses;
const ProgressSummary(
{this.id,this.name,this.access,this.courses});
const ProgressSummary({this.id, this.name, this.access, this.courses});
factory ProgressSummary.fromJson(Map<String, dynamic> json) => _$ProgressSummaryFromJson(json);
factory ProgressSummary.fromJson(Map<String, dynamic> json) =>
_$ProgressSummaryFromJson(json);
Map<String, dynamic> toJson() => _$ProgressSummaryToJson(this);
}

View File

@ -101,7 +101,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,

View File

@ -148,11 +148,10 @@ class LearnService with ListenableServiceMixin {
);
}).toList();
print('MY SUMMARIES - COMPLETED COUNT: ${_modules.first.access?.completedCount}');
print(
'MY SUMMARIES - COMPLETED COUNT: ${_modules.first.access?.completedCount}');
print('PROGRESS PERCENT: ${_modules.first.access?.progressPercent}');
/// UPDATE LESSONS
_lessons = _lessons.map((lesson) {
return lesson.copyWith(
@ -169,8 +168,6 @@ class LearnService with ListenableServiceMixin {
} catch (_) {
return null;
}
}
LearnCourse? getLearnCourseById(int id) {
@ -179,7 +176,5 @@ class LearnService with ListenableServiceMixin {
} catch (_) {
return null;
}
}
}

View File

@ -111,6 +111,5 @@ class OnboardingService with ListenableServiceMixin {
_regions = await _apiService.getEthiopiaRegions();
notifyListeners();
}
}

View File

@ -38,7 +38,8 @@ class CodegenLoader extends AssetLoader{
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
"eight_character_minimum": "ቢያንስ 8 ፊደላት",
"password_match": "የይለፍ ቃሉ ተመሳስሏል",
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
"sign_up_agreement":
"‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
"terms_of_services": "የአገልግሎት ውሎች",
"and": "እና",
"privacy_policy": "የግላዊነት ፖሊሲ",
@ -137,7 +138,8 @@ class CodegenLoader extends AssetLoader{
"call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ",
"tap_to_call": "ለመደወል ይንኩ",
"join_telegram": "በቴሌግራም የይማሩ አካዳሚን ይቀላቀሉ",
"connect_with_support_team": "ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።",
"connect_with_support_team":
"ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።",
"open_in_telegram": "በቴሌግራም ይክፈቱ",
"search_for": "ፈልጉት",
"current_level": "የአሁኑ ደረጃ",
@ -187,7 +189,8 @@ class CodegenLoader extends AssetLoader{
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"favourite_topic":
"የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"your_interest": "ፍላጎትህን ጻፍ…",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
@ -219,13 +222,15 @@ static const Map<String,dynamic> _en = {
"login": "Login",
"register_with_google": "Register with Google",
"register_with_phone": "Register with phone number",
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
"enter_phone_number":
"Enter your phone number. We will send you a confirmation code there.",
"login_with_email": "Login with email",
"create_password": "Create password",
"confirm_password": "Confirm password",
"eight_character_minimum": "8 characters minimum",
"password_match": "password match",
"sign_up_agreement": "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"sign_up_agreement":
"By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"terms_of_services": "Terms of Service",
"and": "and",
"privacy_policy": "Privacy Policy",
@ -236,7 +241,8 @@ static const Map<String,dynamic> _en = {
"code_sent_to_email": "Code sent to your email",
"resend_code_in": "Resend code in",
"reset_password": "Reset Password",
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
"enter_email_reset_code":
"Enter your email. We will send you a reset code.",
"please_wait": "Please wait",
"reset_code_sent": "Reset code sent successfully",
"reset_code": "Reset code",
@ -277,7 +283,8 @@ static const Map<String,dynamic> _en = {
"cancel": "Cancel",
"you_are_speaking": "You're speaking",
"practice_completed": "Practice completed!",
"great_improvement": "You sound more confident this time, great improvement",
"great_improvement":
"You sound more confident this time, great improvement",
"practice_again": "Practice again",
"conversation_review": "Conversation review",
"result": "Result",
@ -324,7 +331,8 @@ static const Map<String,dynamic> _en = {
"call_our_support": "Call our support team between 9 AM - 6 PM",
"tap_to_call": "Tap to call",
"join_telegram": "Join Yimaru Academy on Telegram",
"connect_with_support_team": "Connect with our support team instantly on Telegram for quick assistance and community updates",
"connect_with_support_team":
"Connect with our support team instantly on Telegram for quick assistance and community updates",
"open_in_telegram": "Open in Telegram",
"search_for": "Search for",
"current_level": "Current Level",
@ -332,18 +340,22 @@ static const Map<String,dynamic> _en = {
"no_practice_available": "No practice available!",
"begin_module_practice": "Begin Module Practice",
"lets_practice_lesson": "Lets Practice",
"lets_quickly_review": "Lets quickly review what youve learned in this module!",
"lets_quickly_review":
"Lets quickly review what youve learned in this module!",
"lets_practice_module": "Let's practice what you just learnt!",
"ask_you_few_actions": "Ill ask you a few questions, and you can respond naturally.",
"ask_you_few_actions":
"Ill ask you a few questions, and you can respond naturally.",
"begin_level_practice": "Begin Level Practice",
"lets_practice_course": "Lets Practice Course",
"lets_quick_review": "Lets quickly review what youve learned in this level!",
"lets_quick_review":
"Lets quickly review what youve learned in this level!",
"speaking": "is speaking...",
"you_have_finished_practice": "You have finished your practice",
"view_results": "View My Results",
"sample_answer": "Sample Answer",
"your_answer": "Your Answer",
"sound_confident": "You sound more confident this time - great improvement!",
"sound_confident":
"You sound more confident this time - great improvement!",
"you_have_completed": "Yay, youve completed",
"yes": "Yes",
"no": "No",
@ -354,15 +366,20 @@ static const Map<String,dynamic> _en = {
"phone_must_start_with": "Phone number must start with 251",
"phone_must_be": "Phone number must be 12 digits",
"what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.",
"name_for_personalization":
"Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender.",
"gender_for_personalization":
"Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"age_for_personalization":
"Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"education_for_personalization":
"This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"occupation_for_personalization":
"Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
@ -374,10 +391,13 @@ static const Map<String,dynamic> _en = {
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"favourite_topic":
"Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"want_quick_assessment":
"Want a quick assessment to know your English level?",
"answer_quick_questions":
"Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
@ -386,7 +406,11 @@ static const Map<String,dynamic> _en = {
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish",
"finish_all_practice": "Finish all the practices in the lessons to take this practice"
"finish_all_practice":
"Finish all the practices in the lessons to take this practice"
};
static const Map<String, Map<String, dynamic>> mapLocales = {
"am": _am,
"en": _en
};
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
}

View File

@ -161,7 +161,8 @@ abstract class LocaleKeys {
static const educational_background = 'educational_background';
static const education_for_personalization = 'education_for_personalization';
static const your_occupation = 'your_occupation';
static const occupation_for_personalization = 'occupation_for_personalization';
static const occupation_for_personalization =
'occupation_for_personalization';
static const location = 'location';
static const select_country_region = 'select_country_region';
static const select_country = 'select_country';
@ -186,5 +187,4 @@ abstract class LocaleKeys {
static const ready_to_explore = 'ready_to_explore';
static const finish = 'finish';
static const finish_all_practice = 'finish_all_practice';
}

View File

@ -30,13 +30,12 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer( AccountPrivacyViewModel viewModel) => Container(
Widget _buildScaffoldContainer(AccountPrivacyViewModel viewModel) =>
Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);
Widget _buildScaffold(AccountPrivacyViewModel viewModel) =>
SafeArea(child: _buildBodyWrapper(viewModel));

View File

@ -13,12 +13,9 @@ class ArifPayView extends StackedView<ArifPayViewModel> {
const ArifPayView({Key? key, required this.phone}) : super(key: key);
void _error(ArifPayViewModel viewModel) => viewModel.pop();
Future<void> _success(ArifPayViewModel viewModel) async {
await viewModel.updatePaymentStatus();
await viewModel.replaceWithHome();
@ -49,8 +46,6 @@ class ArifPayView extends StackedView<ArifPayViewModel> {
? const PageLoadingIndicator()
: _buildScaffold(viewModel);
Widget _buildScaffold(ArifPayViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
@ -59,7 +54,8 @@ class ArifPayView extends StackedView<ArifPayViewModel> {
URLRequest(url: WebUri(viewModel.request?.paymentUrl ?? '')),
onUpdateVisitedHistory: (controller, url, androidIsReload) async {
if (url.toString().contains(kSuccessUrl)) {
showSuccessToast('Subscription successful, activation in progress!');
showSuccessToast(
'Subscription successful, activation in progress!');
_success(viewModel);
} else if (url.toString().contains(kErrorUrl)) {
_error(viewModel);

View File

@ -68,11 +68,11 @@ class ArifPayViewModel extends ReactiveViewModel {
}
// Update payment status
Future<void> updatePaymentStatus() async => await runBusyFuture(_updatePaymentStatus(),
Future<void> updatePaymentStatus() async =>
await runBusyFuture(_updatePaymentStatus(),
busyObject: StateObjects.learnSubscription);
Future<void> _updatePaymentStatus() async {
Map<String, dynamic> response = {};
response = await _apiService.getProfileData(_user?.userId);
@ -80,10 +80,6 @@ class ArifPayViewModel extends ReactiveViewModel {
if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User;
await _authenticationService.saveUserData(user);
}
}
}

View File

@ -31,8 +31,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(CallSupportViewModel viewModel) =>
Container(
Widget _buildScaffoldContainer(CallSupportViewModel viewModel) => Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);

View File

@ -16,20 +16,20 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
const LearnCourseView({Key? key, required this.id}) : super(key: key);
Future<void> _onPractice({required BuildContext context,
Future<void> _onPractice(
{required BuildContext context,
required LearnCourse course,
required LearnCourseViewModel viewModel}) async {
if (course.access?.completedCount == course.access?.totalCount) {
await viewModel.navigateToLearnPractice(
id: course.id ?? 0,
level: course.name ?? '');
id: course.id ?? 0, level: course.name ?? '');
} else {
await _showSheet(context: context, viewModel: viewModel);
}
}
Future<void> _showSheet({required BuildContext context,
Future<void> _showSheet(
{required BuildContext context,
required LearnCourseViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
@ -37,7 +37,6 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
builder: (_) => _buildSheet(viewModel),
);
@override
void onViewModelReady(LearnCourseViewModel viewModel) async {
await viewModel.getLearnCourses(id);
@ -49,37 +48,44 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
LearnCourseViewModel();
@override
Widget builder(BuildContext context,
Widget builder(
BuildContext context,
LearnCourseViewModel viewModel,
Widget? child,) =>
Widget? child,
) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context,
Widget _buildScaffoldWrapper(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffoldContainer(context: context, viewModel: viewModel),
);
Widget _buildScaffoldContainer({required BuildContext context,
Widget _buildScaffoldContainer(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Container(
decoration: bgDecoration,
child: _buildScaffold(context: context, viewModel: viewModel),
);
Widget _buildScaffold({required BuildContext context,
Widget _buildScaffold(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
SafeArea(child: _buildBody(context: context, viewModel: viewModel));
Widget _buildBody({required BuildContext context,
Widget _buildBody(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(context: context, viewModel: viewModel),
);
Widget _buildColumn({required BuildContext context,
Widget _buildColumn(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Column(
children: [
@ -90,49 +96,49 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
],
);
Widget _buildAppBar(LearnCourseViewModel viewModel) =>
SmallAppBar(
Widget _buildAppBar(LearnCourseViewModel viewModel) => SmallAppBar(
onPop: viewModel.pop,
showBackButton: true,
);
Widget _buildCoursesColumnWrapper({required BuildContext context,
Widget _buildCoursesColumnWrapper(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Expanded(child: _buildLevelsColumnScrollView(context: context,viewModel: viewModel));
Expanded(
child: _buildLevelsColumnScrollView(
context: context, viewModel: viewModel));
Widget _buildLevelsColumnScrollView({required BuildContext context,
Widget _buildLevelsColumnScrollView(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
SingleChildScrollView(
child: _buildListViewBuilder(context: context, viewModel: viewModel),
);
Widget _buildListViewBuilder({required BuildContext context,
Widget _buildListViewBuilder(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
viewModel.busy(StateObjects.learnCourses)
? _buildProgressIndicator()
: _buildListView(context: context, viewModel: viewModel);
Widget _buildProgressIndicator() =>
const Center(
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView({required BuildContext context,
Widget _buildListView(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
ListView.separated(
shrinkWrap: true,
itemCount: viewModel.courses.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) =>
_buildTile(
itemBuilder: (context, index) => _buildTile(
course: viewModel.courses[index],
onPracticeTap: () async =>
await _onPractice(
onPracticeTap: () async => await _onPractice(
context: context,
viewModel: viewModel,
course: viewModel.courses[index]
),
course: viewModel.courses[index]),
onViewTap: () async =>
await viewModel.navigateToLearnModule(viewModel.courses[index]),
),
@ -150,7 +156,6 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
onPracticeTap: onPracticeTap,
);
Widget _buildSheet(LearnCourseViewModel viewModel) =>
FinishPracticeSheet(onTap: viewModel.pop);
}

View File

@ -12,6 +12,7 @@ import 'package:yimaru_app/ui/widgets/motivation_card.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/finish_practice_sheet.dart';
import '../../widgets/small_app_bar.dart';
import 'learn_lesson_viewmodel.dart';
@ -23,11 +24,15 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
Future<void> _onPractice(
{required int index,
required LearnLesson lesson,
required BuildContext context,
required LearnLessonViewModel viewModel}) async {
if (index > 1) {
if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
}
print(index);
print(viewModel.user?.subscriptionStatus);
/* if (index > 1) {
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'subscribed') {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
@ -35,8 +40,17 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
}
} else {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
}*/
}
}
Future<void> _showSheet(
{required BuildContext context,
required LearnLessonViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel),
);
@override
void onViewModelReady(LearnLessonViewModel viewModel) async {
@ -148,7 +162,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
verticalSpaceLarge,
_buildHeader(),
verticalSpaceMedium,
_buildListViewBuilder(viewModel),
_buildListViewBuilder(context: context,viewModel: viewModel),
];
Widget _buildTitle() => Text(
@ -183,16 +197,18 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
style: style18DG700,
);
Widget _buildListViewBuilder(LearnLessonViewModel viewModel) =>
Widget _buildListViewBuilder( {required BuildContext context,
required LearnLessonViewModel viewModel}) =>
viewModel.busy(StateObjects.learnLessons)
? _buildProgressIndicator()
: _buildListView(viewModel);
: _buildListView(context: context,viewModel: viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder(
Widget _buildListView( {required BuildContext context,
required LearnLessonViewModel viewModel}) => ListView.builder(
shrinkWrap: true,
itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(),
@ -201,10 +217,12 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
lesson: viewModel.lessons[index],
onPracticeTap: () async => await _onPractice(
index: index,
context: context,
viewModel: viewModel,
lesson: viewModel.lessons[index],
),
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
index: index,
module: module,
lesson: viewModel.lessons[index],
hasPractice:
@ -224,4 +242,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
);
Widget _buildSheet(LearnLessonViewModel viewModel) =>
FinishPracticeSheet(onTap: viewModel.pop);
}

View File

@ -24,12 +24,9 @@ class LearnLessonViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>();
@override
List<ListenableServiceMixin> get listenableServices => [_learnService,_authenticationService];
List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService];
// Current user
User? get _user => _authenticationService.user;
@ -49,8 +46,6 @@ class LearnLessonViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
@ -60,15 +55,17 @@ class LearnLessonViewModel extends ReactiveViewModel {
subtitle: LocaleKeys.ask_you_few_actions.tr(),
);
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnLessonDetail(
{required bool hasPractice,
{
required int index,
required bool hasPractice,
required LearnLesson lesson,
required LearnModule module}) async =>
await _navigationService.navigateToLearnLessonDetailView(
index: index,
lesson: lesson, module: module, hasPractice: hasPractice);
// Remote api call
@ -88,6 +85,7 @@ class LearnLessonViewModel extends ReactiveViewModel {
LearnModule? getUpdatedLearnModule(int id) {
return _learnService.getLearnModuleById(id);
}
//Refresh image
Future<void> refreshLessonImages(List<LearnLesson> lessons) async {
for (final lesson in lessons) {

View File

@ -15,20 +15,33 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
final int index;
final bool hasPractice;
final LearnModule module;
final LearnLesson lesson;
const LearnLessonDetailView(
{Key? key,
required this.index,
required this.lesson,
required this.module,
required this.hasPractice})
: super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
Future<void> _onPractice(
{required LearnLesson lesson,
required LearnLessonDetailViewModel viewModel}) async {
await viewModel.pause();
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
/*if (index > 1) {
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'subscribed') {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await viewModel.navigateToLearnSubscription();
}
} else {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
}*/
}
@override
@ -193,6 +206,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
text: LocaleKeys.take_practice.tr(),
onTap: () async => await _navigate(viewModel),
onTap: () async =>
await _onPractice(lesson: lesson, viewModel: viewModel),
);
}

View File

@ -7,7 +7,9 @@ import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/learn_lesson.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart';
import '../../../services/vimeo_service.dart';
@ -26,8 +28,16 @@ class LearnLessonDetailViewModel extends ReactiveViewModel {
final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
@override
List<ListenableServiceMixin> get listenableServices => [_learnService];
List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Learn lessons
List<LearnLesson> get _lessons => _learnService.lessons;
@ -121,6 +131,9 @@ class LearnLessonDetailViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnPractice(int id) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,

View File

@ -151,11 +151,14 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
style: style14P400,
);
Widget _buildOverallProgress(LearnModuleViewModel viewModel) => OverallProgress(
Widget _buildOverallProgress(LearnModuleViewModel viewModel) =>
OverallProgress(
indicatorBackgroundColor: kcWhite,
backgroundColor: kcPrimaryColor.withOpacity(0.1),
progress:
(viewModel.getUpdatedLearnCourse(course.id ?? 0) ?? course).access?.progressPercent ?? 0,
progress: (viewModel.getUpdatedLearnCourse(course.id ?? 0) ?? course)
.access
?.progressPercent ??
0,
);
Widget _buildListViewBuilder(

View File

@ -86,5 +86,4 @@ class LearnModuleViewModel extends ReactiveViewModel {
String getModuleImage(LearnModule module) =>
getReadableUrl(_refreshedIcons[module.id] ?? '') ?? '';
}

View File

@ -169,7 +169,6 @@ class InteractLearnPracticeScreen
Widget _buildSpeakingIndicator(LearnPracticeViewModel viewModel) =>
WaveWrapper(height: 200, child: _buildSpinnerState(viewModel));
Widget _buildSpinnerState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.recordLearnPracticeAnswer) ||
viewModel.busy(StateObjects.finishLearnPracticeQuestion)

View File

@ -15,7 +15,6 @@ class LearnPracticeAppreciationScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeAppreciationScreen({super.key});
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();

View File

@ -163,13 +163,10 @@ class LearnPracticeDescriptionScreen
Widget _buildImage(LearnPracticeViewModel viewModel) => CachedNetworkImage(
fit: BoxFit.cover,
width: double.maxFinite,
imageUrl:
getReadableUrl(viewModel.practices.first.storyImage ?? '') ?? '',
);
Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),

View File

@ -126,7 +126,8 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOccupationForm() => const OccupationFormScreen();
Widget _buildCountryRegionForm() => CountryRegionFormScreen(regionController: regionController);
Widget _buildCountryRegionForm() =>
CountryRegionFormScreen(regionController: regionController);
Widget _buildLearningGoalForm() => const LearningGoalFormScreen();

View File

@ -49,7 +49,6 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
return '';
}
void _pop(OnboardingViewModel viewModel) {
viewModel.resetLearningGoalFormScreen();
viewModel.goBack();
@ -70,20 +69,17 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
Widget build(BuildContext context, OnboardingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) =>
Scaffold(
Widget _buildScaffoldWrapper(OnboardingViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(OnboardingViewModel viewModel) =>
Column(
Widget _buildScaffold(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) =>
[
List<Widget> _buildScaffoldChildren(OnboardingViewModel viewModel) => [
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
@ -97,14 +93,12 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
child: _buildBodyWrapper(viewModel),
);
Widget _buildBodyWrapper(OnboardingViewModel viewModel) =>
Padding(
Widget _buildBodyWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(OnboardingViewModel viewModel) =>
Column(
Widget _buildBody(OnboardingViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
@ -113,23 +107,20 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
List<Widget> _buildBodyChildren(OnboardingViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(OnboardingViewModel viewModel) =>
Column(
Widget _buildUpperColumn(OnboardingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) =>
[
List<Widget> _buildUpperColumnChildren(OnboardingViewModel viewModel) => [
verticalSpaceMedium,
_buildTitle(viewModel),
verticalSpaceMedium,
_buildLearningGoals(viewModel)
];
Widget _buildAppBar(OnboardingViewModel viewModel) =>
LargeAppBar(
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
showBackButton: true,
showLanguageSelection: true,
onPop: () => _pop(viewModel),
@ -139,8 +130,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
: viewModel.selectedLanguage['code'],
);
Widget _buildTitle(OnboardingViewModel viewModel) =>
Text.rich(
Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich(
TextSpan(
text:
'${LocaleKeys.hello.tr()} ${viewModel.userData['first_name']},',
@ -153,25 +143,23 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
]),
);
Widget _buildLearningGoals(OnboardingViewModel viewModel) =>
ListView.builder(
Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder(
shrinkWrap: true,
itemCount: 3, // viewModel.learningGoals.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) =>
_buildLearningGoal(
itemBuilder: (context, index) => _buildLearningGoal(
icon: getIcon(index),
title: getTitles(index),
subtitle: getSubtitle(index),
selected:
viewModel.isSelectedLearningGoal(viewModel.learningGoals[index]),
onTap: () =>
viewModel.setSelectedLearningGoal(
viewModel.learningGoals[index]),
viewModel.setSelectedLearningGoal(viewModel.learningGoals[index]),
),
);
Widget _buildLearningGoal({required String title,
Widget _buildLearningGoal(
{required String title,
required bool selected,
required IconData icon,
required String subtitle,
@ -184,8 +172,7 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
selected: selected,
);
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) =>
Padding(
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);

View File

@ -101,8 +101,7 @@ class PrivacyPolicyView extends StackedView<PrivacyPolicyViewModel> {
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(PrivacyPolicyViewModel viewModel) =>
Container(
Widget _buildScaffoldContainer(PrivacyPolicyViewModel viewModel) => Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);

View File

@ -55,8 +55,10 @@ class ProfileView extends StackedView<ProfileViewModel> {
body: _buildScaffoldContainer(context: context, viewModel: viewModel),
);
Widget _buildScaffoldContainer( {required BuildContext context,
required ProfileViewModel viewModel}) => Container(
Widget _buildScaffoldContainer(
{required BuildContext context,
required ProfileViewModel viewModel}) =>
Container(
decoration: bgDecoration,
child: _buildScaffold(context: context, viewModel: viewModel),
);

View File

@ -56,7 +56,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
'birth_day': DateFormat('yyyy-MM-dd').format(DateTime.now()),
};
viewModel.addUserData(data);
await viewModel.updateProfile();
@ -144,14 +143,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
? const PageLoadingIndicator()
: _buildScaffoldContainer(context: context, viewModel: viewModel);
Widget _buildScaffoldContainer( {required BuildContext context,
required ProfileDetailViewModel viewModel}) => Container(
Widget _buildScaffoldContainer(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>
Container(
decoration: bgDecoration,
child: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack(
{required BuildContext context,
required ProfileDetailViewModel viewModel}) =>

View File

@ -164,7 +164,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
return false;
}
void setRegionFocus() {
_focusRegion = true;
rebuildUi();
@ -258,7 +257,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
// Profile detail fields
Future<void> getProfileDetailFields() async =>
await runBusyFuture(_getProfileDetailFields(),busyObject: StateObjects.profileDetail);
await runBusyFuture(_getProfileDetailFields(),
busyObject: StateObjects.profileDetail);
Future<void> _getProfileDetailFields() async {
await _onboardingService.getProfileDetailFields();

View File

@ -28,8 +28,7 @@ class SupportView extends StackedView<SupportViewModel> {
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(SupportViewModel viewModel) =>
Container(
Widget _buildScaffoldContainer(SupportViewModel viewModel) => Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);

View File

@ -32,7 +32,6 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(TelegramSupportViewModel viewModel) =>
Container(
decoration: bgDecoration,

View File

@ -26,8 +26,6 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
Widget build(BuildContext context, LearnCourseViewModel viewModel) =>
_buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCard(LearnCourseViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(

View File

@ -27,12 +27,14 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
required this.index,
required this.lesson});
@override
Widget build(BuildContext context, LearnLessonViewModel viewModel) =>
_buildContainer(viewModel);
_buildContainerWrapper(viewModel);
Widget _buildContainerWrapper(LearnLessonViewModel viewModel)=> GestureDetector(
onTap: !(lesson.access?.isAccessible ?? false) ? onPracticeTap:null,
child: _buildContainer(viewModel),
);
Widget _buildContainer(LearnLessonViewModel viewModel) => Container(
width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15),
@ -80,8 +82,8 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
children: _buildExpansionTileChildren(viewModel),
);
Widget _buildLeadingWrapper(LearnLessonViewModel viewModel) => MiniThumbnail(
thumbnail: getReadableUrl(lesson.thumbnail ?? '') ?? '');
Widget _buildLeadingWrapper(LearnLessonViewModel viewModel) =>
MiniThumbnail(thumbnail: getReadableUrl(lesson.thumbnail ?? '') ?? '');
Widget _buildTitle() => Text(
lesson.title ?? '',

View File

@ -21,8 +21,6 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
const LearnModuleTile(
{super.key, this.onModuleTap, this.onPracticeTap, required this.module});
@override
Widget build(BuildContext context, LearnModuleViewModel viewModel) =>
_buildExpansionTileCard(context: context, viewModel: viewModel);
@ -90,16 +88,13 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
child: _buildIcon(viewModel),
);
Widget _buildIcon(LearnModuleViewModel viewModel) =>
CachedNetworkImage(
Widget _buildIcon(LearnModuleViewModel viewModel) => CachedNetworkImage(
width: 25,
height: 25,
cacheKey: viewModel.getModuleImage(module),
imageUrl: viewModel.getModuleImage(module),
);
Widget _buildTitleWrapper() => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: _buildTitle(),
@ -214,8 +209,6 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
text: LocaleKeys.take_practice.tr(),
);
Widget _buildContainerShaderState() => !(module.access?.isAccessible ?? false)
? _buildContainerShader()
: Container();

View File

@ -1,5 +1,5 @@
name: yimaru_app
version: 0.1.27+29
version: 0.1.28+30
publish_to: 'none'
description: A new Flutter project.