feat(course): Add lesson level duolingo practice.

This commit is contained in:
BisratHailu 2026-06-10 03:57:03 +03:00
parent 3fc9635207
commit 813720db89
103 changed files with 4220 additions and 2591 deletions

View File

@ -194,5 +194,17 @@
"completed_practices": "Completed Practices", "completed_practices": "Completed Practices",
"total_practices": "Total Practices", "total_practices": "Total Practices",
"progress_percentage": "Progress Percentage", "progress_percentage": "Progress Percentage",
"notifications": "Notifications" "notifications": "Notifications",
"choose_course_to_improve":"Choose a course to improve your professional or exam skills.",
"start_course":"Start course",
"english_proficiency":"English Proficiency Exams",
"select_your_target_exam":"Select your target exam and start preparing",
"take_mock_exam":"Take Mock Exam",
"course_detail":"Course Detail",
"continue_course":"Continue Course",
"module_detail":"Module Detail",
"continue_module":"Continue Module",
"play_video":"Play Video",
"practice_test":"Practice Test",
"start_assessment":"Start assessment"
} }

View File

@ -60,6 +60,7 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
import 'package:yimaru_app/ui/views/notification/notification_view.dart'; import 'package:yimaru_app/ui/views/notification/notification_view.dart';
import 'package:yimaru_app/services/in_app_notification_service.dart'; import 'package:yimaru_app/services/in_app_notification_service.dart';
import 'package:yimaru_app/services/push_notification_service.dart'; import 'package:yimaru_app/services/push_notification_service.dart';
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -100,6 +101,7 @@ import 'package:yimaru_app/services/push_notification_service.dart';
MaterialRoute(page: LearnCourseView), MaterialRoute(page: LearnCourseView),
MaterialRoute(page: PaymentView), MaterialRoute(page: PaymentView),
MaterialRoute(page: NotificationView), MaterialRoute(page: NotificationView),
MaterialRoute(page: CoursePracticeView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
import 'package:json_annotation/json_annotation.dart';
part 'course_practice.g.dart';
@JsonSerializable()
class CoursePractice {
final int? id;
final String? title;
@JsonKey(name: 'lesson_id')
final int? lessonId;
@JsonKey(name: 'persona_id')
final int? personaId;
@JsonKey(name: 'quick_tips')
final String? quickTips;
@JsonKey(name: 'story_image')
final String? storyImage;
@JsonKey(name: 'publish_status')
final String? publishStatus;
@JsonKey(name: 'question_set_id')
final int? questionSetId;
@JsonKey(name: 'story_description')
final String? storyDescription;
const CoursePractice(
{this.id,
this.title,
this.lessonId,
this.quickTips,
this.storyImage,
this.personaId,
this.publishStatus,
this.questionSetId,
this.storyDescription});
factory CoursePractice.fromJson(Map<String, dynamic> json) =>
_$CoursePracticeFromJson(json);
Map<String, dynamic> toJson() => _$CoursePracticeToJson(this);
}

View File

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'course_practice.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CoursePractice _$CoursePracticeFromJson(Map<String, dynamic> json) =>
CoursePractice(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
lessonId: (json['lesson_id'] as num?)?.toInt(),
quickTips: json['quick_tips'] as String?,
storyImage: json['story_image'] as String?,
personaId: (json['persona_id'] as num?)?.toInt(),
publishStatus: json['publish_status'] as String?,
questionSetId: (json['question_set_id'] as num?)?.toInt(),
storyDescription: json['story_description'] as String?,
);
Map<String, dynamic> _$CoursePracticeToJson(CoursePractice instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'lesson_id': instance.lessonId,
'persona_id': instance.personaId,
'quick_tips': instance.quickTips,
'story_image': instance.storyImage,
'publish_status': instance.publishStatus,
'question_set_id': instance.questionSetId,
'story_description': instance.storyDescription,
};

View File

@ -0,0 +1,35 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/dynamic_payload.dart';
part 'course_question.g.dart';
@JsonSerializable()
class CourseQuestion {
final int? id;
final int? points;
final String? status;
@JsonKey(name: 'question_type')
final String? questionType;
@JsonKey(name: 'difficulty_level')
final String? difficultyLevel;
@JsonKey(name: 'dynamic_payload')
final DynamicPayload? dynamicPayload;
const CourseQuestion(
{this.id,
this.status,
this.points,
this.questionType,
this.difficultyLevel,
this.dynamicPayload});
factory CourseQuestion.fromJson(Map<String, dynamic> json) =>
_$CourseQuestionFromJson(json);
Map<String, dynamic> toJson() => _$CourseQuestionToJson(this);
}

View File

@ -0,0 +1,30 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'course_question.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CourseQuestion _$CourseQuestionFromJson(Map<String, dynamic> json) =>
CourseQuestion(
id: (json['id'] as num?)?.toInt(),
status: json['status'] as String?,
points: (json['points'] as num?)?.toInt(),
questionType: json['question_type'] as String?,
difficultyLevel: json['difficulty_level'] as String?,
dynamicPayload: json['dynamic_payload'] == null
? null
: DynamicPayload.fromJson(
json['dynamic_payload'] as Map<String, dynamic>),
);
Map<String, dynamic> _$CourseQuestionToJson(CourseQuestion instance) =>
<String, dynamic>{
'id': instance.id,
'points': instance.points,
'status': instance.status,
'question_type': instance.questionType,
'difficulty_level': instance.difficultyLevel,
'dynamic_payload': instance.dynamicPayload,
};

View File

@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
part 'dynamic_item.g.dart';
@JsonSerializable()
class DynamicItem {
final String id;
final String kind;
final dynamic value;
DynamicItem({
required this.id,
required this.kind,
required this.value,
});
factory DynamicItem.fromJson(Map<String, dynamic> json) =>
_$DynamicItemFromJson(json);
Map<String, dynamic> toJson() => _$DynamicItemToJson(this);
}

View File

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'dynamic_item.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DynamicItem _$DynamicItemFromJson(Map<String, dynamic> json) => DynamicItem(
id: json['id'] as String,
kind: json['kind'] as String,
value: json['value'],
);
Map<String, dynamic> _$DynamicItemToJson(DynamicItem instance) =>
<String, dynamic>{
'id': instance.id,
'kind': instance.kind,
'value': instance.value,
};

View File

@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
import 'dynamic_item.dart';
part 'dynamic_payload.g.dart';
@JsonSerializable()
class DynamicPayload {
final List<DynamicItem>? stimulus;
final List<DynamicItem>? response;
const DynamicPayload({this.stimulus, this.response});
factory DynamicPayload.fromJson(Map<String, dynamic> json) =>
_$DynamicPayloadFromJson(json);
Map<String, dynamic> toJson() => _$DynamicPayloadToJson(this);
}

View File

@ -0,0 +1,23 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'dynamic_payload.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DynamicPayload _$DynamicPayloadFromJson(Map<String, dynamic> json) =>
DynamicPayload(
stimulus: (json['stimulus'] as List<dynamic>?)
?.map((e) => DynamicItem.fromJson(e as Map<String, dynamic>))
.toList(),
response: (json['response'] as List<dynamic>?)
?.map((e) => DynamicItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$DynamicPayloadToJson(DynamicPayload instance) =>
<String, dynamic>{
'stimulus': instance.stimulus,
'response': instance.response,
};

View File

@ -14,6 +14,8 @@ import 'package:yimaru_app/ui/common/app_constants.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../models/course_module.dart'; import '../models/course_module.dart';
import '../models/course_practice.dart';
import '../models/course_question.dart';
import '../models/course_unit.dart'; import '../models/course_unit.dart';
import '../models/field_option.dart'; import '../models/field_option.dart';
import '../models/learn_course.dart'; import '../models/learn_course.dart';
@ -903,7 +905,7 @@ class ApiService {
print('Here'); print('Here');
final Response response = await _service.dio.get( final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl'); '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) { if (response.statusCode == 200) {
var data = response.data; var data = response.data;
@ -1144,6 +1146,55 @@ class ApiService {
} }
} }
// Course practices
Future<List<CoursePractice>> getCoursePractices(int id) async {
try {
List<CoursePractice> practices = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kLessonsUrl/$id/$kPracticesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['practices'] as List;
practices = decodedData.map(
(e) {
return CoursePractice.fromJson(e);
},
).toList();
return practices;
}
return [];
} catch (e) {
return [];
}
}
// Get course questions
Future<List<CourseQuestion>> getCourseQuestions(int id) async {
try {
List<CourseQuestion> questions = [];
print('Here');
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
questions = decodedData.map(
(e) {
return CourseQuestion.fromJson(e);
},
).toList();
return questions;
}
return [];
} catch (e) {
return [];
}
}
// Check update // Check update
Future<Map<String, dynamic>> checkUpdate(Map<String, dynamic> data) async { Future<Map<String, dynamic>> checkUpdate(Map<String, dynamic> data) async {
try { try {

View File

@ -6,6 +6,8 @@ import '../models/course_catalog.dart';
import '../models/course_lesson.dart'; import '../models/course_lesson.dart';
import '../models/course_module.dart'; import '../models/course_module.dart';
import '../models/course_unit.dart'; import '../models/course_unit.dart';
import '../models/refresh_object.dart';
import '../ui/common/enmus.dart';
class CourseService with ListenableServiceMixin { class CourseService with ListenableServiceMixin {
// Dependency injection // Dependency injection
@ -76,4 +78,16 @@ class CourseService with ListenableServiceMixin {
_lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
notifyListeners(); notifyListeners();
} }
Future<String?> refreshObject(String url) async {
Map<String, dynamic> data = {'reference': url};
Map<String, dynamic> response = await _apiService.refreshObject(data);
if (response['status'] == ResponseStatus.success) {
RefreshObject object = response['data'] as RefreshObject;
return object.url ?? '';
}
return null;
}
} }

View File

@ -7,13 +7,15 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await locator<PushNotificationService>().setupFlutterNotifications(); await locator<PushNotificationService>().setupFlutterNotifications();
await locator<PushNotificationService>().showNotification(message); await locator<PushNotificationService>().showNotification(message);
} }
class PushNotificationService { final _messaging = FirebaseMessaging.instance;
bool _isFlutterLocalNotificationInitialized = false; class PushNotificationService {
final _messaging = FirebaseMessaging.instance;
final _localNotifications = FlutterLocalNotificationsPlugin(); bool _isFlutterLocalNotificationInitialized = false;
Future<void> initialize() async { final _localNotifications = FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
// Initialize FCM token // Initialize FCM token
await updateFCMToken(); await updateFCMToken();
@ -27,9 +29,9 @@ Future<void> initialize() async {
// Subscribe to all devices // Subscribe to all devices
subscribeToTopic('yimaru'); subscribeToTopic('yimaru');
} }
Future<void> _requestPermission() async { Future<void> _requestPermission() async {
await _messaging.requestPermission( await _messaging.requestPermission(
alert: true, alert: true,
badge: true, badge: true,
@ -38,9 +40,9 @@ Future<void> _requestPermission() async {
provisional: false, provisional: false,
announcement: false, announcement: false,
criticalAlert: false); criticalAlert: false);
} }
Future<void> setupFlutterNotifications() async { Future<void> setupFlutterNotifications() async {
if (_isFlutterLocalNotificationInitialized) { if (_isFlutterLocalNotificationInitialized) {
return; return;
} }
@ -78,9 +80,9 @@ Future<void> setupFlutterNotifications() async {
); );
_isFlutterLocalNotificationInitialized = true; _isFlutterLocalNotificationInitialized = true;
} }
Future<void> showNotification(RemoteMessage message) async { Future<void> showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification; RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android; AndroidNotification? android = message.notification?.android;
@ -99,9 +101,9 @@ Future<void> showNotification(RemoteMessage message) async {
presentAlert: true, presentBadge: true, presentSound: true)), presentAlert: true, presentBadge: true, presentSound: true)),
); );
} }
} }
Future<void> _setupMessageHandler() async { Future<void> _setupMessageHandler() async {
// Foreground message // Foreground message
FirebaseMessaging.onMessage FirebaseMessaging.onMessage
.listen((RemoteMessage message) => showNotification(message)); .listen((RemoteMessage message) => showNotification(message));
@ -115,22 +117,22 @@ Future<void> _setupMessageHandler() async {
if (initialMessage != null) { if (initialMessage != null) {
_handleBackgroundMessage(initialMessage); _handleBackgroundMessage(initialMessage);
} }
} }
void _handleBackgroundMessage(RemoteMessage message) { void _handleBackgroundMessage(RemoteMessage message) {
if (message.data['type'] == 'Page') { if (message.data['type'] == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName'); // navigatorKey.currentState?.pushNamed('RouteName');
} }
} }
Future<void> subscribeToTopic(String topic) async { Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic); await FirebaseMessaging.instance.subscribeToTopic(topic);
} }
Future<void> updateFCMToken() async { Future<void> updateFCMToken() async {
// print('DEVICE TOKEN: ${await _messaging.getToken()}'); // print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) { _messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken); // updateTokenOnServer(newToken);
}); });
} }
} }

View File

@ -21,7 +21,6 @@ String kLevelsUrl = 'levels';
String kCoursesUrl = 'courses'; String kCoursesUrl = 'courses';
String kModulesUrl = 'modules'; String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons'; String kLessonsUrl = 'lessons';

View File

@ -16,8 +16,8 @@ enum LearnPractices { course, module, lesson }
// Voice recording state // Voice recording state
enum VoiceRecordingState { pending, recording } enum VoiceRecordingState { pending, recording }
// // Levels // Course practice
// enum ProficiencyLevels { a1, a2, b1, b2, none } enum CoursePractices { courseCatalog, unit, lesson }
// Progress status // Progress status
enum ProgressStatuses { pending, started, completed } enum ProgressStatuses { pending, started, completed }
@ -67,10 +67,13 @@ enum StateObjects {
learnPracticeAnswer, learnPracticeAnswer,
loginWithPhoneNumber, loginWithPhoneNumber,
assessmentQuestions, assessmentQuestions,
coursePracticeReview,
learnPracticeQuestion, learnPracticeQuestion,
completeLearnPractice, completeLearnPractice,
coursePracticeQuestion, coursePracticeQuestion,
coursePracticeQuestions, coursePracticeQuestions,
recordLearnPracticeAnswer, recordLearnPracticeAnswer,
finishLearnPracticeQuestion recordCoursePracticeAnswer,
finishLearnPracticeQuestion,
finishCoursePracticeQuestion
} }

View File

@ -3,6 +3,24 @@ import 'dart:ui';
import 'app_colors.dart'; import 'app_colors.dart';
// Seconds to minutes
String getSecondsToMinutes(int totalSeconds) {
final minutes = totalSeconds ~/ 60;
final seconds = totalSeconds % 60;
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
// Minutes
int getMinutes(int totalSeconds) {
return totalSeconds ~/ 60;
}
// Seconds
int getSeconds(int totalSeconds) {
return totalSeconds % 60;
}
// Split full name // Split full name
Map<String, String> splitFullName(String fullName) { Map<String, String> splitFullName(String fullName) {
final parts = fullName.trim().split(RegExp(r'\s+')); final parts = fullName.trim().split(RegExp(r'\s+'));

View File

@ -236,6 +236,11 @@ TextStyle style14B400 = const TextStyle(
color: kcBlue, color: kcBlue,
); );
TextStyle style0Ts = const TextStyle(
fontSize: 0,
color: kcTransparent,
);
TextStyle style14P600 = const TextStyle( TextStyle style14P600 = const TextStyle(
color: kcPrimaryColor, color: kcPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -333,11 +338,8 @@ TextStyle style14LG400 = const TextStyle(
color: kcLightGrey, color: kcLightGrey,
); );
TextStyle style12W600 = const TextStyle( TextStyle style12W600 =
fontSize: 12, const TextStyle(fontSize: 12, color: kcWhite, fontWeight: FontWeight.w600);
color: kcWhite,
fontWeight: FontWeight.w600
);
TextStyle style14MG400 = const TextStyle( TextStyle style14MG400 = const TextStyle(
color: kcMediumGrey, color: kcMediumGrey,

View File

@ -16,10 +16,9 @@ class CourseViewModel extends ReactiveViewModel {
final _inAppNotificationService = locator<InAppNotificationService>(); final _inAppNotificationService = locator<InAppNotificationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_authenticationService,_inAppNotificationService]; [_authenticationService, _inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;
@ -55,6 +54,4 @@ class CourseViewModel extends ReactiveViewModel {
Future<void> navigateToCourseCatalog() async => Future<void> navigateToCourseCatalog() async =>
await _navigationService.navigateToCourseCatalogView(); await _navigationService.navigateToCourseCatalogView();
} }

View File

@ -30,6 +30,8 @@ class CourseModuleViewModel extends ReactiveViewModel {
Future<void> navigateToCourseLessonDetail(CourseLesson lesson) async => Future<void> navigateToCourseLessonDetail(CourseLesson lesson) async =>
await _navigationService.navigateToCourseLessonDetailView(lesson: lesson); await _navigationService.navigateToCourseLessonDetailView(lesson: lesson);
Future<void> navigateToCoursePractice(int id) async =>
await _navigationService.navigateToCoursePracticeView(id: id, practice: CoursePractices.lesson,);
// Remote api call // Remote api call
// Course modules // Course modules

View File

@ -0,0 +1,309 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/models/course_question.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_listening_practice_1_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_listening_practice_2_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_practices_screens.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_finish_screen.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_intro_screen.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_listening_practice_1_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_listening_practice_2_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_listening_practice_3_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_retake_screen.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_1_answer.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_1_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_1_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_2_answer.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_2_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_2_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_3_answer.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_3_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_3_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_4_answer.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_4_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_speaking_practice_4_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_1_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_1_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_2_answer.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_2_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_2_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_3_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_3_review.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_4_question.dart';
import 'package:yimaru_app/ui/views/course_practice/screens/duolingo_writing_practice_4_review.dart';
import '../../common/app_colors.dart';
import '../../common/validators/form_validator.dart';
import '../../widgets/cancel_practice_sheet.dart';
import '../../widgets/page_loading_indicator.dart';
import '../../widgets/practice_loading_screen.dart';
import 'course_practice_view.form.dart';
import 'course_practice_viewmodel.dart';
@FormView(fields: [
FormTextField(name: 'practice', validator: FormValidator.validateForm),
])
class CoursePracticeView extends StackedView<CoursePracticeViewModel> with $CoursePracticeView {
final int id;
final CoursePractices practice;
const CoursePracticeView({
Key? key,
required this.id,
required this.practice,
}) : super(key: key);
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel),
);
Widget _buildQuestionScreen(CoursePracticeViewModel viewModel) {
String type =
viewModel.questions[viewModel.currentQuestion].id.toString() ?? '';
if (type == '636') {
return const DuolingoSpeakingPractice1Question();
} else if (type == 'Read, Then Speak') {
return const DuolingoSpeakingPractice2Question();
} else if (type == 'Speaking Sample') {
return const DuolingoSpeakingPractice3Question();
} else if (type == 'Interactive Speaking') {
return const DuolingoSpeakingPractice4Question();
} else if (type == 'Write About the Photo') {
return DuolingoWritingPractice1Question(
practiceController: practiceController);
} else if (type == 'Writing Sample') {
return DuolingoWritingPractice2Question(
practiceController: practiceController);
} else if (type == 'Interactive Writing Part 1') {
return DuolingoWritingPractice3Question(
practiceController: practiceController);
} else if (type == 'Interactive Writing Part 2') {
return DuolingoWritingPractice4Question(
practiceController: practiceController);
} else if (type == 'Listen and Type') {
return DuolingoListeningPractice1Question(
practiceController: practiceController);
} else if (type == 'Interactive Listening - Part 1') {
return DuolingoListeningPractice2Question(
practiceController: practiceController);
} else if (type == 'Interactive Listening - Part 2') {
return DuolingoListeningPractice3Question(
practiceController: practiceController);
}
return Container();
}
Widget _buildAnswerScreen(CoursePracticeViewModel viewModel) {
String type =
viewModel.questions[viewModel.currentQuestion].id.toString() ?? '';
if (type == '636') {
return const DuolingoSpeakingPractice1Answer();
} else if (type == 'Read, Then Speak') {
return const DuolingoSpeakingPractice2Answer();
} else if (type == 'Speaking Sample') {
return const DuolingoSpeakingPractice3Answer();
} else if (type == 'Interactive Speaking') {
return const DuolingoSpeakingPractice4Answer();
} else if (type == 'Write About the Photo') {
return Container();
} else if (type == 'Writing Sample') {
return DuolingoWritingPractice2Answer(
practiceController: practiceController);
} else if (type == 'Interactive Writing Part 1') {
return Container();
} else if (type == 'Interactive Writing Part 2') {
return Container();
} else if (type == 'Listen and Type') {
return Container();
} else if (type == 'Interactive Listening - Part 1') {
return Container();
}
return Container();
}
Widget _buildReviewScreen(CoursePracticeViewModel viewModel) {
String type =
viewModel.questions[viewModel.currentQuestion].id.toString() ?? '';
if (type == '636') {
return const DuolingoSpeakingPractice1Review();
} else if (type == 'Read, Then Speak') {
return const DuolingoSpeakingPractice2Review();
} else if (type == 'Speaking Sample') {
return const DuolingoSpeakingPractice3Review();
} else if (type == 'Interactive Speaking') {
return const DuolingoSpeakingPractice4Review();
} else if (type == 'Write About the Photo') {
return DuolingoWritingPractice1Review(
practiceController: practiceController);
} else if (type == 'Writing Sample') {
return DuolingoWritingPractice2Review(
practiceController: practiceController);
} else if (type == 'Interactive Writing Part 1') {
return DuolingoWritingPractice3Review(
practiceController: practiceController);
} else if (type == 'Interactive Writing Part 2') {
return DuolingoWritingPractice4Review(
practiceController: practiceController);
} else if (type == 'Listen and Type') {
return DuolingoListeningPractice1Review(
practiceController: practiceController);
} else if (type == 'Interactive Listening - Part 1') {
return DuolingoListeningPractice2Review(
practiceController: practiceController);
}
return Container();
}
@override
void onViewModelReady(CoursePracticeViewModel viewModel) async {
await viewModel.getCoursePractices(id: id, practice: practice);
super.onViewModelReady(viewModel);
}
@override
CoursePracticeViewModel viewModelBuilder(BuildContext context) =>
CoursePracticeViewModel();
@override
Widget builder(
BuildContext context,
CoursePracticeViewModel viewModel,
Widget? child,
) =>
_buildPracticeScreensWrapper(context: context, viewModel: viewModel);
Widget _buildPracticeScreensWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, data) {
if (!didPop) {
Future.microtask(() async =>
await _showSheet(context: context, viewModel: viewModel));
}
},
child: _buildScaffoldWrapper(viewModel));
Widget _buildSheet(CoursePracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildBodyState(viewModel),
);
Widget _buildBodyState(CoursePracticeViewModel viewModel) =>
viewModel.busy(StateObjects.coursePractice)
? const PageLoadingIndicator()
: viewModel.practices.isEmpty || viewModel.questions.isEmpty
? _buildPageLoadingIndicator(viewModel)
: _buildBody(viewModel);
Widget _buildPageLoadingIndicator(CoursePracticeViewModel viewModel) =>
PracticeLoadingScreen(
isLoading: viewModel.busy(StateObjects.coursePractice),
onTap: () async =>
await viewModel.getCoursePractices(id: id, practice: practice),
onPop: viewModel.practices.isEmpty || viewModel.questions.isEmpty
? viewModel.pop
: null,
isEmpty: viewModel.practices.isEmpty || viewModel.questions.isEmpty,
);
Widget _buildBody(CoursePracticeViewModel viewModel) => IndexedStack(
index: viewModel.currentPage, children: _buildBodyChildren(viewModel));
List<Widget> _buildBodyChildren(CoursePracticeViewModel viewModel) => [
if (practice != CoursePractices.lesson)
_buildDuolingoAssessmentsScreen(),
_buildQuestionSetView(viewModel)
];
Widget _buildQuestionSetView(CoursePracticeViewModel viewModel) =>
PageView.builder(
itemCount: viewModel.questions.length,
controller: viewModel.practiceController,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) => _buildQuestionView(
index: index + 1,
viewModel: viewModel,
question: viewModel.questions[index]),
);
Widget _buildQuestionView(
{required int index,
required CourseQuestion question,
required CoursePracticeViewModel viewModel}) =>
PageView(
controller: viewModel.questionController,
physics: const NeverScrollableScrollPhysics(),
children: _buildScreens(
index: index, viewModel: viewModel, question: question),
);
List<Widget> _buildScreens(
{required int index,
required CourseQuestion question,
required CoursePracticeViewModel viewModel}) =>
[
_buildDuolingoIntroScreen(viewModel),
_buildDuolingoQuestionScreen(viewModel),
_buildDuolingoAnswerScreen(viewModel),
_buildDuolingoReviewScreen(viewModel),
_buildDuolingoRetakeScreen(viewModel),
_buildDuolingoFinishScreen(viewModel),
];
Widget _buildDuolingoAssessmentsScreen() => const DuolingoPracticesScreens();
Widget _buildDuolingoIntroScreen(CoursePracticeViewModel viewModel) =>
DuolingoIntroScreen(
type: viewModel.selectedQuestionParam['type'],
title: viewModel.selectedQuestionParam['intro_title'],
subtitle: viewModel.selectedQuestionParam['intro_subtitle']);
Widget _buildDuolingoQuestionScreen(CoursePracticeViewModel viewModel) =>
_buildQuestionScreen(viewModel);
Widget _buildDuolingoAnswerScreen(CoursePracticeViewModel viewModel) =>
_buildAnswerScreen(viewModel);
Widget _buildDuolingoReviewScreen(CoursePracticeViewModel viewModel) =>
_buildReviewScreen(viewModel);
Widget _buildDuolingoRetakeScreen(CoursePracticeViewModel viewModel) =>
DuolingoRetakeScreen(
title: viewModel.selectedQuestionParam['outro_title'],
subtitle: viewModel.selectedQuestionParam['outro_subtitle']);
Widget _buildDuolingoFinishScreen(CoursePracticeViewModel viewModel) =>
DuolingoFinishScreen(
title: viewModel.selectedQuestionParam['outro_title'],
subtitle: viewModel.selectedQuestionParam['outro_subtitle']);
}

View File

@ -0,0 +1,180 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
// **************************************************************************
// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/validators/form_validator.dart';
const bool _autoTextFieldValidation = true;
const String PracticeValueKey = 'practice';
final Map<String, TextEditingController>
_CoursePracticeViewTextEditingControllers = {};
final Map<String, FocusNode> _CoursePracticeViewFocusNodes = {};
final Map<String, String? Function(String?)?>
_CoursePracticeViewTextValidations = {
PracticeValueKey: FormValidator.validateForm,
};
mixin $CoursePracticeView {
TextEditingController get practiceController =>
_getFormTextEditingController(PracticeValueKey);
FocusNode get practiceFocusNode => _getFormFocusNode(PracticeValueKey);
TextEditingController _getFormTextEditingController(
String key, {
String? initialValue,
}) {
if (_CoursePracticeViewTextEditingControllers.containsKey(key)) {
return _CoursePracticeViewTextEditingControllers[key]!;
}
_CoursePracticeViewTextEditingControllers[key] =
TextEditingController(text: initialValue);
return _CoursePracticeViewTextEditingControllers[key]!;
}
FocusNode _getFormFocusNode(String key) {
if (_CoursePracticeViewFocusNodes.containsKey(key)) {
return _CoursePracticeViewFocusNodes[key]!;
}
_CoursePracticeViewFocusNodes[key] = FocusNode();
return _CoursePracticeViewFocusNodes[key]!;
}
/// Registers a listener on every generated controller that calls [model.setData()]
/// with the latest textController values
void syncFormWithViewModel(FormStateHelper model) {
practiceController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
}
/// Registers a listener on every generated controller that calls [model.setData()]
/// with the latest textController values
@Deprecated(
'Use syncFormWithViewModel instead.'
'This feature was deprecated after 3.1.0.',
)
void listenToFormUpdated(FormViewModel model) {
practiceController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
}
/// Updates the formData on the FormViewModel
void _updateFormData(FormStateHelper model, {bool forceValidate = false}) {
model.setData(
model.formValueMap
..addAll({
PracticeValueKey: practiceController.text,
}),
);
if (_autoTextFieldValidation || forceValidate) {
updateValidationData(model);
}
}
bool validateFormFields(FormViewModel model) {
_updateFormData(model, forceValidate: true);
return model.isFormValid;
}
/// Calls dispose on all the generated controllers and focus nodes
void disposeForm() {
// The dispose function for a TextEditingController sets all listeners to null
for (var controller in _CoursePracticeViewTextEditingControllers.values) {
controller.dispose();
}
for (var focusNode in _CoursePracticeViewFocusNodes.values) {
focusNode.dispose();
}
_CoursePracticeViewTextEditingControllers.clear();
_CoursePracticeViewFocusNodes.clear();
}
}
extension ValueProperties on FormStateHelper {
bool get hasAnyValidationMessage => this
.fieldsValidationMessages
.values
.any((validation) => validation != null);
bool get isFormValid {
if (!_autoTextFieldValidation) this.validateForm();
return !hasAnyValidationMessage;
}
String? get practiceValue => this.formValueMap[PracticeValueKey] as String?;
set practiceValue(String? value) {
this.setData(
this.formValueMap..addAll({PracticeValueKey: value}),
);
if (_CoursePracticeViewTextEditingControllers.containsKey(
PracticeValueKey)) {
_CoursePracticeViewTextEditingControllers[PracticeValueKey]?.text =
value ?? '';
}
}
bool get hasPractice =>
this.formValueMap.containsKey(PracticeValueKey) &&
(practiceValue?.isNotEmpty ?? false);
bool get hasPracticeValidationMessage =>
this.fieldsValidationMessages[PracticeValueKey]?.isNotEmpty ?? false;
String? get practiceValidationMessage =>
this.fieldsValidationMessages[PracticeValueKey];
}
extension Methods on FormStateHelper {
void setPracticeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PracticeValueKey] = validationMessage;
/// Clears text input fields on the Form
void clearForm() {
practiceValue = '';
}
/// Validates text input fields on the Form
void validateForm() {
this.setValidationMessages({
PracticeValueKey: getValidationMessage(PracticeValueKey),
});
}
}
/// Returns the validation message for the given key
String? getValidationMessage(String key) {
final validatorForKey = _CoursePracticeViewTextValidations[key];
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_CoursePracticeViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;
}
/// Updates the fieldsValidationMessages on the FormViewModel
void updateValidationData(FormStateHelper model) =>
model.setValidationMessages({
PracticeValueKey: getValidationMessage(PracticeValueKey),
});

View File

@ -0,0 +1,536 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:waveform_recorder/waveform_recorder.dart';
import 'package:yimaru_app/models/course_practice.dart';
import '../../../app/app.locator.dart';
import '../../../models/course_question.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/audio_player_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/course_service.dart';
import '../../../services/status_checker_service.dart';
import '../../../services/voice_recorder_service.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/helper_functions.dart';
class CoursePracticeViewModel extends ReactiveViewModel
with FormStateHelper
implements FormViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _dialogService = locator<DialogService>();
final _courseService = locator<CourseService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _audioPlayerService = locator<AudioPlayerService>();
final _voiceRecorderService = locator<VoiceRecorderService>();
final _authenticationService = locator<AuthenticationService>();
CoursePracticeViewModel() {
_listenToAudio();
}
@override
List<ListenableServiceMixin> get listenableServices => [
_courseService,
_audioPlayerService,
_voiceRecorderService,
_authenticationService
];
// User
User? get _user => _authenticationService.user;
User? get user => _user;
// AudioPlayer
AudioPlayer get _player => _audioPlayerService.player;
AudioPlayer get player => _player;
Duration _duration = Duration.zero;
Duration _position = Duration.zero;
Duration get position => _position;
Duration get duration => _duration;
double get progress {
if (_duration.inMilliseconds == 0) return 0;
return _position.inMilliseconds / _duration.inMilliseconds;
}
// Voice recorder
String? _recordedAudio;
String? get recordedAudio => _recordedAudio;
WaveformRecorderController get _waveController =>
_voiceRecorderService.waveController;
WaveformRecorderController get waveController => _waveController;
// Voice recorder state
VoiceRecordingState get _recordingState =>
_voiceRecorderService.recordingState;
VoiceRecordingState get recordingState => _recordingState;
// Busy object
String? _busyObject;
String? get busyObject => _busyObject;
Voice? _playing;
Voice? get playing => _playing;
// Speaking state
bool _isSpeaking = false;
bool get isSpeaking => _isSpeaking;
// Course practices
bool _focusPractice = false;
bool get focusPractice => _focusPractice;
List<CoursePractice> _practices = [];
List<CoursePractice> get practices => _practices;
// Practice questions
String? _refreshedUrl;
String? get refreshedUrl => _refreshedUrl;
int _currentQuestion = 0;
int get currentQuestion => _currentQuestion;
List<CourseQuestion> _questions = [];
List<CourseQuestion> get questions => _questions;
final List<Map<String, dynamic>> _questionParams = [
{
'label': 'Speaking 01',
'type': DuolingoAssessments.speaking,
'intro_title': 'Speak About the Photo',
'outro_title': 'Speaking Practice Completed',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
'intro_subtitle':
'Prepare to speak for at least 30 seconds about the photo you are shown'
},
{
'label': 'Speaking 02',
'intro_title': 'Read, Then Speak',
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': 'You will speak about the given topic',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
},
{
'label': 'Speaking 03',
'intro_title': 'Speaking Sample',
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': 'Youll speak for 13 minutes about a given topic.',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
},
{
'label': 'Speaking 04',
'type': DuolingoAssessments.speaking,
'intro_title': 'Interactive Speaking',
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': ' Youll answer a series of short questions.',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
},
{
'label': 'Writing 05',
'type': DuolingoAssessments.writing,
'intro_title': 'Write About the Photo',
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
'You will see a picture and write a short description based on what you observe. Focus on clear, simple sentences.'
},
{
'label': 'Writing 06',
'intro_title': 'Writing Sample',
'type': DuolingoAssessments.writing,
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
'You will write a longer response based on a given question. Your writing will be shared with institutions as part of your score.'
},
{
'label': 'Writing 07',
'type': DuolingoAssessments.writing,
'outro_title': 'Writing Practice Completed',
'intro_title': 'Interactive Writing Part 1',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
' You will write short and simple sentences. Focus on basic ideas and clear meaning. Write naturally and manage your time.'
},
{
'label': 'Writing 08',
'type': DuolingoAssessments.writing,
'intro_title': 'Interactive Writing Part 2',
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
' You will continue writing on a related idea. Add more details using clear sentences. Stay focused and complete your response within the time.'
},
{
'label': 'Listening 09',
'intro_title': 'Listen and Type',
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_subtitle':
'You will hear a short audio clip. Type exactly what you hear.',
'outro_subtitle': 'Youve finished this Listening session. Great work!',
},
{
'label': 'Listening 10',
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_title': 'Interactive Listening - Part 1',
'intro_subtitle': ' Listen carefully and complete the missing words.',
'outro_subtitle': 'Youve finished this Listening session. Great work!',
},
{
'label': 'Listening 11',
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_title': 'Interactive Listening - Part 2',
'intro_subtitle': 'Listen and choose the correct option.',
'outro_subtitle': 'Youve finished this Listening session. Great work!',
},
{
'label': 'Assessment 12',
'type': DuolingoAssessments.listening,
'title': 'Interactive Listening - Part 3',
'outro_title': 'Listening Practice Completed',
'subtitle': 'Write a summary of the conversation you just had',
'outro_subtitle': 'Youve finished this Listening session. Great work!',
},
{
'label': 'Reading 13',
'intro_title': 'Read and Select',
'type': DuolingoAssessments.reading,
'intro_subtitle':
'Read the sentence and select the option that correctly completes the meaning.'
},
{
'label': 'Reading 14',
'intro_title': 'Fill in the blank',
'type': DuolingoAssessments.reading,
'intro_subtitle': 'Complete the sentences by filling in the missing words'
},
];
List<Map<String, dynamic>> get questionParams => _questionParams;
// Selected question param
Map<String, dynamic> _selectedQuestionParam = {
'label': 'Speaking 01',
'intro_title': 'Speak About the Photo',
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
'intro_subtitle':
'Prepare to speak for at least 30 seconds about the photo you are shown',
};
Map<String, dynamic> get selectedQuestionParam => _selectedQuestionParam;
// Practice answers
final List<Map<String, dynamic>> _answers = [];
List<Map<String, dynamic>> get answers => _answers;
// Next button state
bool _buttonActive = false;
bool get buttonActive => _buttonActive;
// In-app navigation
int _currentPage = 0;
int get currentPage => _currentPage;
final PageController _practiceController = PageController();
PageController get practiceController => _practiceController;
final PageController _questionController = PageController();
PageController get questionController => _questionController;
// Practice
void setPracticeFocus() {
_focusPractice = true;
rebuildUi();
}
// Speaking state
void setSpeakingState() {
_isSpeaking = !_isSpeaking;
rebuildUi();
}
// Next button
void setNextButton() {
_buttonActive = true;
rebuildUi();
}
// Question param
Future<void> setQuestionParam(String type) async {
print('FIRST QUESTION: $type');
if (type == '636') {
// await refreshQuestionUrl(_questions[_currentQuestion]);
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else if (type == '') {
_selectedQuestionParam = _questionParams.elementAt(0);
} else {
_selectedQuestionParam = _questionParams.elementAt(0);
}
}
// Voice recorder
Future<void> stopRecording() async {
if (_voiceRecorderService.waveController.isRecording) {
await _voiceRecorderService.stopRecording();
_recordedAudio = await _voiceRecorderService.getRecordedAudio();
}
}
Future<void> startRecording() async => await runBusyFuture(_startRecording(),
busyObject: StateObjects.recordCoursePracticeAnswer);
Future<void> _startRecording() async =>
await _voiceRecorderService.startRecording();
// Play practice audio
void _listenToAudio() {
_audioPlayerService.durationStream.listen((dur) {
if (dur.inMilliseconds > 0) {
_duration = dur;
rebuildUi();
}
});
_audioPlayerService.positionStream.listen((pos) {
_position = pos;
rebuildUi();
});
}
Future<void> playVoicePrompt(CourseQuestion question) async =>
await runBusyFuture(_playVoicePrompt(question),
busyObject: StateObjects.coursePracticeQuestion);
Future<void> _playVoicePrompt(CourseQuestion question) async {
_questionController.jumpToPage(1);
await _audioPlayerService
.playUrl(question.dynamicPayload?.stimulus?.first.value ?? '');
}
Future<void> replayVoicePrompt(CourseQuestion question) async {
await _audioPlayerService
.playUrl(question.dynamicPayload?.stimulus?.first.value ?? '');
}
Future<void> playResult(Voice voice) async {
setBusyObject(voice);
await playAudio(voice);
}
Future<void> playAudio(Voice voice) async =>
await runBusyFuture(_playAudio(voice),
busyObject: StateObjects.coursePracticeReview);
Future<void> _playAudio(Voice voice) async {
if (voice == Voice.recorded) {
print('RECORDED: ${_recordedAudio ?? ''}');
await _audioPlayerService.playLocal(_recordedAudio ?? '');
} else {
String url = await getRefreshedUrl(_questions[currentQuestion]
.dynamicPayload
?.response
?.last
.value ??
'') ??
'';
print('REFRESHED: ${_questions[currentQuestion]
.dynamicPayload
?.response
?.last
.value ??
''}');
await _audioPlayerService.playUrl(url);
}
}
Future<void> pauseAudio() async {
await _audioPlayerService.pause();
}
// Set busy object
void setBusyObject(Voice playing) {
_playing = playing;
rebuildUi();
}
// Dialogue
Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog(
cancelTitle: 'No',
title: 'Recording',
buttonTitle: 'Yes',
barrierDismissible: true,
cancelTitleColor: kcDarkGrey,
buttonTitleColor: kcPrimaryColor,
description: 'Are you sure you want to stop recording?',
);
return response?.confirmed;
}
// In-app navigation
void nextScreen() {
_questionController.nextPage(
curve: Curves.easeInOutCubic,
duration: const Duration(milliseconds: 350),
);
}
void nextPage() {
_currentPage++;
rebuildUi();
}
void goTo(int page) {
_currentPage = page;
rebuildUi();
}
void goBack() {
if (_currentPage == 0) {
pop();
} else {
_currentPage--;
rebuildUi();
}
}
Future<void> nextQuestion(
{required int index, required CourseQuestion question}) async =>
await runBusyFuture(_nextQuestion(index: index, question: question),
busyObject: StateObjects.finishCoursePracticeQuestion);
Future<void> _nextQuestion(
{required int index, required CourseQuestion question}) async {
await stopRecording();
_answers.add({
'busy_object': question.id.toString(),
'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(),
'sample_text_answer':
question.dynamicPayload?.response?.first.value ?? '',
'sample_voice_answer':
question.dynamicPayload?.stimulus?.first.value ?? '',
});
if (index != _questions.length) {
_practiceController.nextPage(
curve: Curves.easeInOutCubic,
duration: const Duration(milliseconds: 350),
);
await playVoicePrompt(_questions[index]);
} else {
goTo(3);
}
}
// Navigation
void pop() => _navigationService.back();
// Remote api call
// Learn practice
Future<void> getCoursePractices(
{required int id, required CoursePractices practice}) async =>
await runBusyFuture(_getCoursePractices(id: id, practice: practice),
busyObject: StateObjects.coursePractice);
Future<void> _getCoursePractices(
{required int id, required CoursePractices practice}) async {
if (await _statusChecker.checkConnection()) {
if (practice == CoursePractices.courseCatalog) {
_practices = await _apiService.getCoursePractices(id);
// await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else if (practice == CoursePractices.unit) {
_practices = await _apiService.getCoursePractices(id);
// await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else {
_practices = await _apiService.getCoursePractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
await setQuestionParam(
_questions[_currentQuestion].id.toString() ?? '');
}
}
}
Future<void> _getLearnPracticeQuestions(int id) async {
_questions = await _apiService.getCourseQuestions(id);
}
// Refresh url
Future<String?> getRefreshedUrl(String value) async {
final String? refreshedUrl = await _courseService.refreshObject(value);
if (refreshedUrl != null) {
return refreshedUrl;
} else {
return null;
}
}
String getReadableImage(String image)=> getReadableUrl(image) ?? '';
}

View File

@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../course_practice_viewmodel.dart';
class DuolingoFinishScreen extends ViewModelWidget<CoursePracticeViewModel> {
final String title;
final String subtitle;
const DuolingoFinishScreen(
{super.key, required this.title, required this.subtitle});
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(context: context, viewModel: viewModel),
);
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context,
required CoursePracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildSafeWrapper(context: context, viewModel: viewModel),
);
Widget _buildSafeWrapper({required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(child: _buildScaffold(context: context, viewModel: viewModel));
Widget _buildScaffold({required BuildContext context,
required CoursePracticeViewModel viewModel}) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildScaffoldChildren({required BuildContext context,
required CoursePracticeViewModel viewModel}) => [
_buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(CoursePracticeViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(CoursePracticeViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(CoursePracticeViewModel viewModel) => [
verticalSpaceMassive,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
];
Widget _buildAppBar({required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
DuolingoPracticeAppBar(
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/complete.svg',
);
Widget _buildTitle() => Text(
title,
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
onTap: viewModel.pop,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
);
}

View File

@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/duolingo_practice_app_bar.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/wave_wrapper.dart';
import '../course_practice_viewmodel.dart';
class DuolingoIntroScreen extends ViewModelWidget<CoursePracticeViewModel> {
final String title;
final String subtitle;
final DuolingoAssessments type;
const DuolingoIntroScreen(
{super.key,
required this.type,
required this.title,
required this.subtitle});
IconData _getIcon() {
if (type == DuolingoAssessments.speaking) {
return Icons.waves;
} else if (type == DuolingoAssessments.writing) {
return Iconsax.pen_add;
} else if (type == DuolingoAssessments.listening) {
return Icons.hearing;
} else {
return Icons.book;
}
}
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context:context, viewModel:viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(context:context, viewModel:viewModel),
);
Widget _buildScaffold( {required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(child: _buildBodyColumnWrapper(context:context, viewModel:viewModel));
Widget _buildBodyColumnWrapper( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(context:context, viewModel:viewModel),
);
Widget _buildBodyColumn( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(context:context, viewModel:viewModel),
);
List<Widget> _buildBodyColumnChildren( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => [
_buildAppBarWrapper(context:context, viewModel:viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(context:context, viewModel:viewModel),
verticalSpaceMedium,
],
);
Widget _buildAppBar( {required BuildContext context,
required CoursePracticeViewModel viewModel}) => DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(CoursePracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
);
List<Widget> _buildSpeakingIndicatorChildren() => [
_buildSpeakingIconWrapper(),
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
];
Widget _buildTitle() => Text(
title,
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14DG400,
textAlign: TextAlign.center,
);
Widget _buildSpeakingIconWrapper() =>
WaveWrapper(height: 125, child: _buildSpeakingIcon());
Widget _buildSpeakingIcon() => Icon(
_getIcon(),
size: 30,
color: kcPrimaryColor,
);
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
text: 'Start Preparation',
onTap: viewModel.nextScreen,
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,82 +1,84 @@
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/views/course_practice/course_practice_view.form.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart'; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import 'package:yimaru_app/ui/widgets/listenable_assessment_card.dart'; import 'package:yimaru_app/ui/widgets/listenable_practice_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoListeningAssessment1Question class DuolingoListeningPractice1Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoListeningAssessment1Question( const DuolingoListeningPractice1Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Listening Assessment', DuolingoPracticeAppBar(
title: 'Listening practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildQuestion(), _buildQuestion(),
verticalSpaceLarge, verticalSpaceLarge,
_buildLabel(), _buildLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildAssessmentFormField(viewModel), _buildPracticeFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasPracticeValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasPracticeValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
_buildAssessmentWrapper(viewModel), _buildPracticeWrapper(viewModel),
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -85,7 +87,7 @@ class DuolingoListeningAssessment1Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const ListenableAssessmentCard(); Widget _buildQuestion() => const ListenablePracticeCard();
Widget _buildLabel() => Text( Widget _buildLabel() => Text(
'Your Answer', 'Your Answer',
@ -93,34 +95,35 @@ class DuolingoListeningAssessment1Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildPracticeFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) => Widget _buildPracticeWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage viewModel.hasPracticeValidationMessage
? _buildAssessmentValidator(viewModel) ? _buildPracticeValidator(viewModel)
: Container(); : Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text( Widget _buildPracticeValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!, viewModel.practiceValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,48 +1,48 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/listenable_assessment_card.dart'; import '../../../widgets/listenable_practice_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoListeningAssessment1Review class DuolingoListeningPractice1Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoListeningAssessment1Review( const DuolingoListeningPractice1Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,20 +50,22 @@ class DuolingoListeningAssessment1Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel), child: _buildQuestionSectionWrapper(viewModel),
); );
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -71,7 +73,7 @@ class DuolingoListeningAssessment1Review
); );
List<Widget> _buildQuestionQuestionSectionChildren( List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) => CoursePracticeViewModel viewModel) =>
[ [
_buildTitleWrapper(), _buildTitleWrapper(),
verticalSpaceMedium, verticalSpaceMedium,
@ -100,7 +102,7 @@ class DuolingoListeningAssessment1Review
child: _buildQuestion(), child: _buildQuestion(),
); );
Widget _buildQuestion() => const ListenableAssessmentCard(); Widget _buildQuestion() => const ListenablePracticeCard();
Widget _buildLabelWrapper() => Padding( Widget _buildLabelWrapper() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
@ -113,24 +115,24 @@ class DuolingoListeningAssessment1Review
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel), child: _buildAssessmentFormField(viewModel),
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -0,0 +1,153 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/listenable_practice_card.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../course_practice_viewmodel.dart';
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.form.dart';
class DuolingoListeningPractice2Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoListeningPractice2Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Listening practice',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceLarge,
_buildLabel('1. Focus of the conference:'),
verticalSpaceTiny,
_buildPracticeFormField(viewModel),
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
_buildPracticeWrapper(viewModel),
verticalSpaceSmall,
_buildLabel('2. Number of presentations:'),
verticalSpaceTiny,
_buildPracticeFormField(viewModel),
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
_buildPracticeWrapper(viewModel),
verticalSpaceSmall,
_buildLabel('3. Keynote speakers field:'),
verticalSpaceTiny,
_buildPracticeFormField(viewModel),
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
_buildPracticeWrapper(viewModel),
];
Widget _buildTitle() => Text(
'Listen to the audio message and type your response.',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const ListenablePracticeCard();
Widget _buildLabel(String question) => Text(
question,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildPracticeFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: practiceController.text.isNotEmpty),
);
Widget _buildPracticeWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasPracticeValidationMessage
? _buildPracticeValidator(viewModel)
: Container();
Widget _buildPracticeValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.practiceValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,48 +1,48 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/listenable_assessment_card.dart'; import '../../../widgets/listenable_practice_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoListeningAssessment2Review class DuolingoListeningPractice2Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoListeningAssessment2Review( const DuolingoListeningPractice2Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,20 +50,22 @@ class DuolingoListeningAssessment2Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel), child: _buildQuestionSectionWrapper(viewModel),
); );
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -71,7 +73,7 @@ class DuolingoListeningAssessment2Review
); );
List<Widget> _buildQuestionQuestionSectionChildren( List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) => CoursePracticeViewModel viewModel) =>
[ [
_buildTitleWrapper(), _buildTitleWrapper(),
verticalSpaceMedium, verticalSpaceMedium,
@ -79,17 +81,17 @@ class DuolingoListeningAssessment2Review
verticalSpaceLarge, verticalSpaceLarge,
_buildLabelWrapper('1. Focus of the conference:'), _buildLabelWrapper('1. Focus of the conference:'),
verticalSpaceTiny, verticalSpaceTiny,
_buildAssessmentFormFieldWrapper(viewModel), _buildPracticeFormFieldWrapper(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildLabelWrapper('2. Number of presentations:'), _buildLabelWrapper('2. Number of presentations:'),
verticalSpaceTiny, verticalSpaceTiny,
_buildAssessmentFormFieldWrapper(viewModel), _buildPracticeFormFieldWrapper(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildLabelWrapper('3. Keynote speakers field:'), _buildLabelWrapper('3. Keynote speakers field:'),
verticalSpaceTiny, verticalSpaceTiny,
_buildAssessmentFormFieldWrapper(viewModel), _buildPracticeFormFieldWrapper(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
_buildAssessmentReviewSection(viewModel) _buildPracticeReviewSection(viewModel)
]; ];
Widget _buildTitleWrapper() => Padding( Widget _buildTitleWrapper() => Padding(
@ -108,7 +110,7 @@ class DuolingoListeningAssessment2Review
child: _buildQuestion(), child: _buildQuestion(),
); );
Widget _buildQuestion() => const ListenableAssessmentCard(); Widget _buildQuestion() => const ListenablePracticeCard();
Widget _buildLabelWrapper(String question) => Padding( Widget _buildLabelWrapper(String question) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
@ -121,22 +123,22 @@ class DuolingoListeningAssessment2Review
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) => Widget _buildPracticeFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel), child: _buildPracticeFormField(viewModel),
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildPracticeFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildPracticeReviewSection(CoursePracticeViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -1,85 +1,86 @@
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/widgets/dwarf_tile.dart'; import 'package:yimaru_app/ui/widgets/dwarf_tile.dart';
import 'package:yimaru_app/ui/widgets/listenable_assessment_card.dart'; import 'package:yimaru_app/ui/widgets/listenable_practice_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/custom_small_radio_button.dart'; import '../../../widgets/custom_small_radio_button.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoListeningAssessment3Question class DuolingoListeningPractice3Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoListeningAssessment3Question( const DuolingoListeningPractice3Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildAppBarChildren(viewModel), children: _buildAppBarChildren(viewModel),
); );
List<Widget> _buildAppBarChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildAppBarChildren(CoursePracticeViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBar(viewModel), child: _buildAppBar(viewModel),
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Listening Assessment', DuolingoPracticeAppBar(
title: 'Listening practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionIndenter(viewModel), child: _buildQuestionIndenter(viewModel),
); );
Widget _buildQuestionIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildQuestionIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildQuestionWrapper(viewModel), child: _buildQuestionWrapper(viewModel),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
verticalSpaceLarge, verticalSpaceLarge,
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
@ -87,7 +88,7 @@ class DuolingoListeningAssessment3Question
verticalSpaceLarge, verticalSpaceLarge,
_buildLabel(), _buildLabel(),
verticalSpaceTiny, verticalSpaceTiny,
_buildAssessmentQuestions(viewModel), // _buildPracticeQuestions(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
@ -99,10 +100,10 @@ class DuolingoListeningAssessment3Question
); );
Widget _buildQuestion() => Widget _buildQuestion() =>
DwarfTile(small: true, child: _buildListenableAssessmentCard()); DwarfTile(small: true, child: _buildListenablePracticeCard());
Widget _buildListenableAssessmentCard() => Widget _buildListenablePracticeCard() =>
const Expanded(child: ListenableAssessmentCard()); const Expanded(child: ListenablePracticeCard());
Widget _buildLabel() => Text( Widget _buildLabel() => Text(
'Select the best response', 'Select the best response',
@ -110,21 +111,21 @@ class DuolingoListeningAssessment3Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildAssessmentQuestions(DuolingoViewModel viewModel) => // Widget _buildPracticeQuestions(CoursePracticeViewModel viewModel) =>
ListView.builder( // ListView.builder(
shrinkWrap: true, // shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), // physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.listeningAssessments.length, // itemCount: viewModel.questions.length,
itemBuilder: (context, index) => _buildAssessmentCard( // itemBuilder: (context, index) => _buildAssessmentCard(
title: viewModel.listeningAssessments[index], // title: viewModel.listeningAssessments[index],
selected: viewModel.isSelectedListeningAssessment( // selected: viewModel.isSelectedListeningAssessment(
viewModel.listeningAssessments[index]), // viewModel.listeningAssessments[index]),
onTap: () => viewModel.setSelectedListeningAssessment( // onTap: () => viewModel.setSelectedListeningAssessment(
viewModel.listeningAssessments[index]), // viewModel.listeningAssessments[index]),
), // ),
); // );
Widget _buildAssessmentCard( Widget _buildPracticeCard(
{required String title, {required String title,
required bool selected, required bool selected,
required GestureTapCallback onTap}) => required GestureTapCallback onTap}) =>
@ -134,12 +135,13 @@ class DuolingoListeningAssessment3Question
selected: selected, selected: selected,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -3,31 +3,32 @@ import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_card.dart'; import '../../../widgets/duolingo_practice_card.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> { class DuolingoPracticesScreens
const DuolingoAssessmentsScreens({super.key}); extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoPracticesScreens({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel)); SafeArea(child: _buildBody(viewModel));
Widget _buildBody(DuolingoViewModel viewModel) => Padding( Widget _buildBody(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel), child: _buildColumn(viewModel),
); );
Widget _buildColumn(DuolingoViewModel viewModel) => Column( Widget _buildColumn(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -36,25 +37,27 @@ class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => const SmallAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) => const SmallAppBar(
showBackButton: false, showBackButton: false,
); );
Widget _buildPracticeColumnWrapper(DuolingoViewModel viewModel) => Widget _buildPracticeColumnWrapper(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildPracticeColumnScrollView(viewModel)); Expanded(child: _buildPracticeColumnScrollView(viewModel));
Widget _buildPracticeColumnScrollView(DuolingoViewModel viewModel) => Widget _buildPracticeColumnScrollView(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildPracticeColumn(viewModel), child: _buildPracticeColumn(viewModel),
); );
Widget _buildPracticeColumn(DuolingoViewModel viewModel) => Column( Widget _buildPracticeColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPracticeColumnChildren(viewModel), children: _buildPracticeColumnChildren(viewModel),
); );
List<Widget> _buildPracticeColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildPracticeColumnChildren(
CoursePracticeViewModel viewModel) =>
[
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
_buildSubtitle(), _buildSubtitle(),
@ -72,14 +75,15 @@ class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
style: style14DG400, style: style14DG400,
); );
Widget _buildListView(DuolingoViewModel viewModel) => GridView.builder( Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.assessments.length, itemCount: viewModel.practices.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildCard( itemBuilder: (context, index) => _buildCard(
title: viewModel.assessments[index]['label'], title: viewModel.practices[index].title ?? '', onTap: () {}
onTap: () => viewModel.setSelectedAssessment( // onTap: () => viewModel.setSelectedAssessment(
page: 1, assessment: viewModel.assessments[index])), // page: 1, assessment: viewModel.assessments[index])
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
mainAxisSpacing: 15, mainAxisSpacing: 15,
@ -92,7 +96,7 @@ class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
required String title, required String title,
required GestureTapCallback onTap, required GestureTapCallback onTap,
}) => }) =>
DuolingoAssessmentCard( DuolingoPracticeCard(
title: title, title: title,
onTap: onTap, onTap: onTap,
); );

View File

@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../course_practice_viewmodel.dart';
class DuolingoRetakeScreen extends ViewModelWidget<CoursePracticeViewModel> {
final String title;
final String subtitle;
const DuolingoRetakeScreen(
{super.key, required this.title, required this.subtitle});
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(context: context, viewModel: viewModel),
);
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context,
required CoursePracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildSafeWrapper(context: context, viewModel: viewModel),
);
Widget _buildSafeWrapper({required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(child: _buildScaffold(context: context, viewModel: viewModel));
Widget _buildScaffold({required BuildContext context,
required CoursePracticeViewModel viewModel}) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildScaffoldChildren({required BuildContext context,
required CoursePracticeViewModel viewModel}) => [
_buildAppBar(context: context, viewModel: viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody( CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper( CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody( CoursePracticeViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(
CoursePracticeViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn( CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren( CoursePracticeViewModel viewModel) => [
verticalSpaceMassive,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
];
Widget _buildAppBar( {required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
DuolingoPracticeAppBar(
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/complete.svg',
);
Widget _buildTitle() => Text(
title,
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildLowerColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(CoursePracticeViewModel viewModel) => [
_buildPracticeButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildPracticeButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
borderRadius: 12,
text: 'Practice Again',
foregroundColor: kcWhite,
// onTap: () => viewModel.goTo(0),
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: viewModel.nextScreen,
foregroundColor: kcPrimaryColor,
);
}

View File

@ -0,0 +1,207 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:waveform_recorder/waveform_recorder.dart';
import 'package:yimaru_app/ui/widgets/countdown_timer.dart';
import 'package:yimaru_app/ui/widgets/speaking_indicator.dart';
import 'package:yimaru_app/ui/widgets/speaking_label.dart';
import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingPractice1Answer
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice1Answer({
super.key,
});
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(context: context, viewModel: viewModel),
);
Widget _buildScaffold(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(
child:
_buildBodyColumnWrapper(context: context, viewModel: viewModel));
Widget _buildBodyColumnWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(context: context, viewModel: viewModel),
);
Widget _buildBodyColumn(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:
_buildBodyColumnChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyColumnChildren(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
[
_buildAppBarWrapper(context: context, viewModel: viewModel),
_buildSpeakingIndicatorColumn(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
children: [
verticalSpaceMedium,
_buildAppBar(context: context, viewModel: viewModel),
verticalSpaceSmall,
_buildCountdownWrapper(),
],
);
Widget _buildAppBar(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(CoursePracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildSpeakingIndicatorColumn(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(viewModel),
);
List<Widget> _buildSpeakingIndicatorChildren(
CoursePracticeViewModel viewModel) =>
[
_buildTitle(),
verticalSpaceMedium,
_buildImageContainer(viewModel),
verticalSpaceSmall,
_buildSpeakerLabelState(viewModel),
verticalSpaceSmall,
_buildSpeakingIndicatorState(viewModel)
];
Widget _buildCountdownWrapper() =>
Align(alignment: Alignment.centerRight, child:_buildCountdown());
Widget _buildCountdown()=> const CountdownTimer();
Widget _buildTitle() => Text(
'Speak about the photo below',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildImageContainer(CoursePracticeViewModel viewModel) => SizedBox(
width: 250,
height: 300,
child: _buildImageWrapper(viewModel),
);
Widget _buildImageWrapper(CoursePracticeViewModel viewModel) => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(viewModel),
);
Widget _buildImage(CoursePracticeViewModel viewModel) => CachedNetworkImage(
fit: BoxFit.fill,
width: double.maxFinite,
imageUrl: viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.first.value ??
'',
);
Widget _buildSpeakerLabelState(CoursePracticeViewModel viewModel) =>
VoiceRecordingState.recording == viewModel.recordingState
? _buildSpeakerLabel()
: const SizedBox(height: 20);
Widget _buildSpeakerLabel() => const SpeakingLabel();
Widget _buildSpeakingIndicatorState(CoursePracticeViewModel viewModel) =>
VoiceRecordingState.recording == viewModel.recordingState
? _buildSpeakingIndicatorWrapper(viewModel)
: Container();
Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Container(
width: 100,
padding: const EdgeInsets.only(right: 15),
child: _buildSpeakingIndicator(viewModel),
);
Widget _buildSpeakingIndicator(CoursePracticeViewModel viewModel) =>
WaveformRecorder(
height: 35,
waveColor: kcPrimaryColor,
durationTextStyle: style0Ts,
controller: viewModel.waveController,
);
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: viewModel.buttonActive
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.buttonActive ? viewModel.nextScreen : null,
text: viewModel.buttonActive
? 'Summit'
: 'You can submit after ${viewModel.questions[viewModel.currentQuestion].dynamicPayload?.response?.first.value['seconds']}sec',
);
}

View File

@ -0,0 +1,238 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timer_countdown/flutter_timer_countdown.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/helper_functions.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingPractice1Question
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice1Question({
super.key,
});
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
Future<void> _next(CoursePracticeViewModel viewModel)async{
await viewModel.startRecording();
viewModel.nextScreen();
}
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack( {required BuildContext context,
required CoursePracticeViewModel viewModel})=> Stack(
children: [
_buildScaffold(context: context,viewModel: viewModel),
_buildStartRecState(viewModel)
],
);
Widget _buildScaffold(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(
child:
_buildBodyColumnWrapper(context: context, viewModel: viewModel));
Widget _buildBodyColumnWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(context: context, viewModel: viewModel),
);
Widget _buildBodyColumn(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:
_buildBodyColumnChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyColumnChildren(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
[
_buildAppBarWrapper(context: context, viewModel: viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
children: [
verticalSpaceMedium,
_buildAppBar(context: context, viewModel: viewModel),
],
);
Widget _buildAppBar(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(CoursePracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(viewModel),
);
List<Widget> _buildSpeakingIndicatorChildren(
CoursePracticeViewModel viewModel) =>
[
_buildTitle(),
verticalSpaceMedium,
_buildTimerStackWrapper(viewModel),
verticalSpaceSmall,
_buildSubtitle(),
verticalSpaceSmall,
_buildImageContainer(viewModel),
];
Widget _buildTitle() => Text(
'Prepare to speak about the photo',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildTimerStackWrapper(CoursePracticeViewModel viewModel) => SizedBox(
height: 50,
child: _buildTimerStack(viewModel),
);
Widget _buildTimerStack(CoursePracticeViewModel viewModel) => Stack(
alignment: Alignment.center,
children: _buildTimerStackChildren(viewModel),
);
List<Widget> _buildTimerStackChildren(CoursePracticeViewModel viewModel) =>
[_buildTimer(), _buildCountdownTime(viewModel)];
Widget _buildTimer() => const CircularProgressIndicator(
value: 1.0,
strokeWidth: 5,
color: kcPrimaryColor,
padding: EdgeInsets.zero,
constraints: BoxConstraints(minWidth: 50, minHeight: 50),
);
Widget _buildCountdownTime(CoursePracticeViewModel viewModel) =>
TimerCountdown(
spacerWidth: 0,
enableDescriptions: false,
timeTextStyle: style14P600,
onEnd: viewModel.nextScreen,
colonsTextStyle: style14P400,
format: CountDownTimerFormat.minutesSeconds,
endTime: DateTime.now().add(
Duration(
minutes: getMinutes(
viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.last.value['seconds'],
),
seconds: getSeconds(
viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.last.value['seconds'],
),
),
),
);
Widget _buildSubtitle() => Text(
'Prep time',
style: style16DG400,
textAlign: TextAlign.center,
);
Widget _buildImageContainer(CoursePracticeViewModel viewModel) => SizedBox(
width: 250,
height: 300,
child: _buildImageWrapper(viewModel),
);
Widget _buildImageWrapper(CoursePracticeViewModel viewModel) => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(viewModel),
);
Widget _buildImage(CoursePracticeViewModel viewModel) => CachedNetworkImage(
fit: BoxFit.fill,
width: double.maxFinite,
imageUrl: viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.first.value ??
'',
);
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Start Speaking',
foregroundColor: kcWhite,
onTap: () => _next(viewModel),
backgroundColor: kcPrimaryColor,
);
Widget _buildStartRecState(CoursePracticeViewModel viewModel) =>
viewModel.busy(StateObjects.recordCoursePracticeAnswer)
? const PageLoadingIndicator()
: Container();
}

View File

@ -0,0 +1,165 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/helper_functions.dart';
import 'package:yimaru_app/ui/widgets/countdown_timer.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/speaking_practice_review_section.dart';
import '../../duolingo/duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingPractice1Review
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice1Review({
super.key,
});
Future<void> _cancel(CoursePracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
viewModel.pop();
}
Future<void> _showSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(context: context, viewModel: viewModel),
);
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(context: context, viewModel: viewModel),
);
Widget _buildScaffold(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
SafeArea(child: _buildBodyColumn(context: context, viewModel: viewModel));
Widget _buildBodyColumn(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children:
_buildBodyColumnChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyColumnChildren(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
[
_buildAppBarIndenter(context: context, viewModel: viewModel),
_buildImageSectionIndenter(viewModel),
_buildPracticeReviewSection(viewModel)
];
Widget _buildAppBarIndenter(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(context: context, viewModel: viewModel),
);
Widget _buildAppBarWrapper(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
Column(
children: [
verticalSpaceMedium,
_buildAppBar(context: context, viewModel: viewModel),
verticalSpaceSmall,
_buildCountdownWrapper(viewModel),
],
);
Widget _buildAppBar(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () async =>
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(
{required BuildContext context,
required CoursePracticeViewModel viewModel}) =>
CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildImageSectionIndenter(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildImageSectionWrapper(viewModel),
);
Widget _buildImageSectionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildImageSectionChildren(viewModel),
);
List<Widget> _buildImageSectionChildren(CoursePracticeViewModel viewModel) =>
[
_buildTitle(),
verticalSpaceMedium,
_buildImageContainer(viewModel),
];
Widget _buildCountdownWrapper(CoursePracticeViewModel viewModel) => Align(
alignment: Alignment.centerRight,
child: _buildCountdown(viewModel),
);
Widget _buildCountdown(CoursePracticeViewModel viewModel) =>
const CountdownTimer(time: '0:0');
Widget _buildTitle() => Text(
'Speak about the photo below',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildImageContainer(CoursePracticeViewModel viewModel) => SizedBox(
width: 150,
height: 200,
child: _buildImageWrapper(viewModel),
);
Widget _buildImageWrapper(CoursePracticeViewModel viewModel) => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(viewModel),
);
Widget _buildImage(CoursePracticeViewModel viewModel) => CachedNetworkImage(
fit: BoxFit.fill,
width: double.maxFinite,
imageUrl: viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.first.value ??
'',
);
Widget _buildPracticeReviewSection(CoursePracticeViewModel viewModel) =>
SpeakingPracticeReviewSection(onTap: viewModel.nextScreen,);
}

View File

@ -1,49 +1,49 @@
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/widgets/duolingo_assessment_question_card.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_question_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart'; import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart'; import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment2Answer class DuolingoSpeakingPractice2Answer
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment2Answer({super.key}); const DuolingoSpeakingPractice2Answer({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel), _buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -52,12 +52,14 @@ class DuolingoSpeakingAssessment2Answer
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Speaking Assessment', DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column( Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(), children: _buildSpeakingIndicatorChildren(),
@ -89,7 +91,7 @@ class DuolingoSpeakingAssessment2Answer
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?', title: 'How has growing up in your hometown influenced you?',
subtitle: subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?', 'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
@ -101,12 +103,13 @@ class DuolingoSpeakingAssessment2Answer
size: 25, size: 25,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,17 +1,17 @@
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/widgets/duolingo_assessment_question_card.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_question_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart'; import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment2Question class DuolingoSpeakingPractice2Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment2Question({ const DuolingoSpeakingPractice2Question({
super.key, super.key,
}); });
@ -52,8 +52,8 @@ class DuolingoSpeakingAssessment2Question
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Speaking Assessment', title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
@ -85,7 +85,7 @@ class DuolingoSpeakingAssessment2Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?', title: 'How has growing up in your hometown influenced you?',
subtitle: subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?', 'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',

View File

@ -4,44 +4,44 @@ import 'package:yimaru_app/ui/widgets/countdown_timer.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/speaking_assessment_review_section.dart'; import '../../../widgets/speaking_practice_review_section.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment2Review class DuolingoSpeakingPractice2Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment2Review({super.key}); const DuolingoSpeakingPractice2Review({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildImageSectionIndenter(viewModel), _buildImageSectionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel) _buildPracticeReviewSection(viewModel)
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,17 +50,19 @@ class DuolingoSpeakingAssessment2Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildImageSectionIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildImageSectionIndenter(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildImageSectionWrapper(viewModel), child: _buildImageSectionWrapper(viewModel),
); );
Widget _buildImageSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildImageSectionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildImageSectionChildren(), children: _buildImageSectionChildren(),
@ -88,12 +90,12 @@ class DuolingoSpeakingAssessment2Review
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?', title: 'How has growing up in your hometown influenced you?',
subtitle: subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?', 'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildPracticeReviewSection(CoursePracticeViewModel viewModel) =>
SpeakingAssessmentReviewSection(onTap: () => viewModel.goTo(5)); SpeakingPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -1,19 +1,19 @@
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/widgets/duolingo_assessment_question_card.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_question_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart'; import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart'; import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart'; import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment3Answer class DuolingoSpeakingPractice3Answer
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment3Answer({super.key}); const DuolingoSpeakingPractice3Answer({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, DuolingoViewModel viewModel) =>
@ -52,8 +52,8 @@ class DuolingoSpeakingAssessment3Answer
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Speaking Assessment', title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
@ -74,7 +74,7 @@ class DuolingoSpeakingAssessment3Answer
Widget _buildCountdownWrapper() => Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer()); const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle: subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?', 'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
); );

View File

@ -1,49 +1,49 @@
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/widgets/duolingo_assessment_question_card.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_question_card.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment3Question class DuolingoSpeakingPractice3Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment3Question({ const DuolingoSpeakingPractice3Question({
super.key, super.key,
}); });
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel), _buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -52,12 +52,14 @@ class DuolingoSpeakingAssessment3Question
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Speaking Assessment', DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column( Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(), children: _buildSpeakingIndicatorChildren(),
@ -85,17 +87,18 @@ class DuolingoSpeakingAssessment3Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle: subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?', 'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/speaking_practice_review_section.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingPractice3Review
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice3Review({super.key});
@override
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildQuestionIndenter(viewModel),
_buildPracticeReviewSection(viewModel)
];
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceSmall,
],
);
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildQuestion(),
);
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
);
Widget _buildPracticeReviewSection(CoursePracticeViewModel viewModel) =>
SpeakingPracticeReviewSection(onTap: () => viewModel.goTo(5));
}

View File

@ -4,46 +4,46 @@ import 'package:yimaru_app/ui/widgets/wave_wrapper.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart'; import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart'; import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment4Answer class DuolingoSpeakingPractice4Answer
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment4Answer({super.key}); const DuolingoSpeakingPractice4Answer({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildAnswerSectionWrapper(viewModel), _buildAnswerSectionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -52,15 +52,17 @@ class DuolingoSpeakingAssessment4Answer
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Speaking Assessment', DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildCountdownWrapper() => Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer()); const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildAnswerSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAnswerSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildAnswerSectionChildren(), children: _buildAnswerSectionChildren(),
@ -96,12 +98,13 @@ class DuolingoSpeakingAssessment4Answer
color: kcWhite, color: kcWhite,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -4,17 +4,17 @@ import 'package:yimaru_app/ui/widgets/dwarf_tile.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart'; import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart'; import '../../../widgets/speaking_indicator.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment4Question class DuolingoSpeakingPractice4Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment4Question({super.key}); const DuolingoSpeakingPractice4Question({super.key});
void _goTo(DuolingoViewModel viewModel) { void _goTo(CoursePracticeViewModel viewModel) {
viewModel.setSpeakingState(); viewModel.setSpeakingState();
if (!viewModel.isSpeaking) { if (!viewModel.isSpeaking) {
viewModel.goTo(3); viewModel.goTo(3);
@ -22,34 +22,34 @@ class DuolingoSpeakingAssessment4Question
} }
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorState(viewModel), _buildSpeakingIndicatorState(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -57,17 +57,20 @@ class DuolingoSpeakingAssessment4Question
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
title: 'Speaking Assessment', DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildSpeakingIndicatorState(DuolingoViewModel viewModel) => Widget _buildSpeakingIndicatorState(CoursePracticeViewModel viewModel) =>
viewModel.isSpeaking // viewModel.isSpeaking
? _buildSpeakingState() // ? _buildSpeakingState()
: _buildQuestionSectionWrapper(viewModel); // :
_buildQuestionSectionWrapper(viewModel);
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionSectionChildren(), children: _buildQuestionSectionChildren(),
@ -105,12 +108,13 @@ class DuolingoSpeakingAssessment4Question
size: 75, size: 75,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Start', text: 'Start',

View File

@ -3,44 +3,44 @@ import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/speaking_assessment_review_section.dart'; import '../../../widgets/speaking_practice_review_section.dart';
import '../../../widgets/dwarf_tile.dart'; import '../../../widgets/dwarf_tile.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment4Review class DuolingoSpeakingPractice4Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingAssessment4Review({super.key}); const DuolingoSpeakingPractice4Review({super.key});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildQuestionIndenter(viewModel), _buildQuestionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel) _buildAssessmentReviewSection(viewModel)
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -48,12 +48,13 @@ class DuolingoSpeakingAssessment4Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildQuestionIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildDwarfWrapper(), child: _buildDwarfWrapper(),
); );
@ -69,6 +70,6 @@ class DuolingoSpeakingAssessment4Review
style: style16DG600, style: style16DG600,
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
SpeakingAssessmentReviewSection(onTap: () => viewModel.goTo(5)); SpeakingPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -4,73 +4,74 @@ import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment1Question class DuolingoWritingPractice1Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment1Question( const DuolingoWritingPractice1Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment', title: 'Writing Assessment',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildImageContainer(), _buildImageContainer(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAssessmentFormField(viewModel), _buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
_buildAssessmentWrapper(viewModel), _buildAssessmentWrapper(viewModel),
]; ];
@ -97,34 +98,35 @@ class DuolingoWritingAssessment1Question
width: double.maxFinite, width: double.maxFinite,
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel) ? _buildAssessmentValidator(viewModel)
: Container(); : Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text( Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!, viewModel.assessmentValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,18 +1,18 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../duolingo_viewmodel.dart'; import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoWritingAssessment1Review class DuolingoWritingPractice1Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment1Review( const DuolingoWritingPractice1Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, DuolingoViewModel viewModel) =>
@ -49,7 +49,7 @@ class DuolingoWritingAssessment1Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
@ -112,13 +112,13 @@ class DuolingoWritingAssessment1Review
maxLines: 5, maxLines: 5,
enabled: false, enabled: false,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -4,75 +4,76 @@ import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Answer class DuolingoWritingPractice2Answer
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment2Answer( const DuolingoWritingPractice2Answer(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment', title: 'Writing Assessment',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildQuestion(), _buildQuestion(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAssessmentFormField(viewModel), _buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
_buildAssessmentWrapper(viewModel), _buildAssessmentWrapper(viewModel),
]; ];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment2Answer
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle: subtitle:
'Describe a memorable experience from your school or work life and explain why it was meaningful to you.'); 'Describe a memorable experience from your school or work life and explain why it was meaningful to you.');
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel) ? _buildAssessmentValidator(viewModel)
: Container(); : Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text( Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!, viewModel.assessmentValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -3,65 +3,66 @@ import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Question class DuolingoWritingPractice2Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment2Question( const DuolingoWritingPractice2Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment', title: 'Writing Assessment',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceMedium,
@ -81,16 +82,17 @@ class DuolingoWritingAssessment2Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle: subtitle:
'Describe a memorable experience from your school or work life and explain why it was meaningful to you.'); 'Describe a memorable experience from your school or work life and explain why it was meaningful to you.');
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,48 +1,48 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Review class DuolingoWritingPractice2Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment2Review( const DuolingoWritingPractice2Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment2Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel), child: _buildQuestionSectionWrapper(viewModel),
); );
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel), children: _buildQuestionQuestionSectionChildren(viewModel),
); );
List<Widget> _buildQuestionQuestionSectionChildren( List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) => CoursePracticeViewModel viewModel) =>
[ [
verticalSpaceLarge, verticalSpaceLarge,
_buildTitle(), _buildTitle(),
@ -93,28 +95,28 @@ class DuolingoWritingAssessment2Review
child: _buildQuestion(), child: _buildQuestion(),
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle: subtitle:
'Describe a memorable experience from your school or work life and explain why it was meaningful to you.'); 'Describe a memorable experience from your school or work life and explain why it was meaningful to you.');
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel), child: _buildAssessmentFormField(viewModel),
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
enabled: false, enabled: false,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -4,75 +4,76 @@ import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment3Question class DuolingoWritingPractice3Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment3Question( const DuolingoWritingPractice3Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment', title: 'Writing Assessment',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildQuestion(), _buildQuestion(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAssessmentFormField(viewModel), _buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
_buildAssessmentWrapper(viewModel), _buildAssessmentWrapper(viewModel),
]; ];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment3Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: '1. Can you tell me about your favorite type of music?', title: '1. Can you tell me about your favorite type of music?',
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel) ? _buildAssessmentValidator(viewModel)
: Container(); : Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text( Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!, viewModel.assessmentValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,48 +1,48 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment3Review class DuolingoWritingPractice3Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment3Review( const DuolingoWritingPractice3Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment3Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel), child: _buildQuestionSectionWrapper(viewModel),
); );
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel), children: _buildQuestionQuestionSectionChildren(viewModel),
); );
List<Widget> _buildQuestionQuestionSectionChildren( List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) => CoursePracticeViewModel viewModel) =>
[ [
verticalSpaceLarge, verticalSpaceLarge,
_buildTitle(), _buildTitle(),
@ -93,27 +95,27 @@ class DuolingoWritingAssessment3Review
child: _buildQuestion(), child: _buildQuestion(),
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'Can you tell me about your favorite type of music?'); title: 'Can you tell me about your favorite type of music?');
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel), child: _buildAssessmentFormField(viewModel),
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
enabled: false, enabled: false,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -4,75 +4,76 @@ import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment4Question class DuolingoWritingPractice4Question
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment4Question( const DuolingoWritingPractice4Question(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel)); SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel), _buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel), _buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment', title: 'Writing Assessment',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel), children: _buildQuestionChildren(viewModel),
); );
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildQuestion(), _buildQuestion(),
verticalSpaceMedium, verticalSpaceMedium,
_buildAssessmentFormField(viewModel), _buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage && if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment) viewModel.focusPractice)
_buildAssessmentWrapper(viewModel), _buildAssessmentWrapper(viewModel),
]; ];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment4Question
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: '1. Can you tell me about your favorite type of music?', title: '1. Can you tell me about your favorite type of music?',
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
onTap: viewModel.setAssessmentFocus, onTap: viewModel.setPracticeFocus,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel) ? _buildAssessmentValidator(viewModel)
: Container(); : Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text( Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!, viewModel.assessmentValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding( Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel), child: _buildContinueButton(viewModel),
); );
Widget _buildContinueButton(DuolingoViewModel viewModel) => Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Submit', text: 'Submit',

View File

@ -1,48 +1,48 @@
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/widgets/duolingo_assessment_review_section.dart'; import 'package:yimaru_app/ui/widgets/duolingo_practice_review_section.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart'; import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart'; import '../../../widgets/duolingo_practice_question_card.dart';
import '../duolingo_viewmodel.dart'; import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment4Review class DuolingoWritingPractice4Review
extends ViewModelWidget<DuolingoViewModel> { extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController assessmentController; final TextEditingController practiceController;
const DuolingoWritingAssessment4Review( const DuolingoWritingPractice4Review(
{super.key, required this.assessmentController}); {super.key, required this.practiceController});
@override @override
Widget build(BuildContext context, DuolingoViewModel viewModel) => Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(DuolingoViewModel viewModel) => Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel)); SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column( Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel), _buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel), _buildExpandedBody(viewModel),
]; ];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding( Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel), child: _buildAppBarWrapper(viewModel),
); );
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column( Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment4Review
], ],
); );
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar( Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback', title: 'Feedback',
onClose: () => viewModel.goTo(0), onClose: () => viewModel.goTo(0),
); );
Widget _buildExpandedBody(DuolingoViewModel viewModel) => Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel)); Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) => Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel), child: _buildQuestionSectionWrapper(viewModel),
); );
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column( Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel), children: _buildQuestionQuestionSectionChildren(viewModel),
); );
List<Widget> _buildQuestionQuestionSectionChildren( List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) => CoursePracticeViewModel viewModel) =>
[ [
verticalSpaceLarge, verticalSpaceLarge,
_buildTitle(), _buildTitle(),
@ -93,27 +95,27 @@ class DuolingoWritingAssessment4Review
child: _buildQuestion(), child: _buildQuestion(),
); );
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard( Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'Can you tell me about your favorite type of music?'); title: 'Can you tell me about your favorite type of music?');
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) => Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel), child: _buildAssessmentFormField(viewModel),
); );
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) => Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField( TextFormField(
maxLines: 5, maxLines: 5,
enabled: false, enabled: false,
maxLength: 250, maxLength: 250,
controller: assessmentController, controller: practiceController,
decoration: inputDecoration( decoration: inputDecoration(
focus: true, focus: true,
hint: 'Start writing here...', hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty), filled: practiceController.text.isNotEmpty),
); );
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) => Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5)); DuolingoPracticeReviewSection(onTap: () => viewModel.goTo(5));
} }

View File

@ -2,37 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart'; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_assessments_screens.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_finish_screen.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_intro_screen.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_listening_assessment_1_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_listening_assessment_1_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_listening_assessment_2_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_listening_assessment_2_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_listening_assessment_3_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_retake_screen.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_1_answer.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_1_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_1_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_2_answer.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_2_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_2_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_3_answer.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_3_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_3_review.dart'
hide DuolingoSpeakingAssessment2Question;
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_4_answer.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_4_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_speaking_assessment_4_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_1_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_1_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_2_answer.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_2_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_2_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_3_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_3_review.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_4_question.dart';
import 'package:yimaru_app/ui/views/duolingo/screens/duolingo_writing_assessment_4_review.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/validators/form_validator.dart'; import '../../common/validators/form_validator.dart';
@ -44,130 +14,7 @@ import 'duolingo_viewmodel.dart';
class DuolingoView extends StackedView<DuolingoViewModel> with $DuolingoView { class DuolingoView extends StackedView<DuolingoViewModel> with $DuolingoView {
const DuolingoView({Key? key}) : super(key: key); const DuolingoView({Key? key}) : super(key: key);
Widget _buildQuestionScreen(DuolingoViewModel viewModel) {
if (viewModel.selectedAssessment['intro_title'] ==
'Speak About the Photo') {
return const DuolingoSpeakingAssessment1Question();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Read, Then Speak') {
return const DuolingoSpeakingAssessment2Question();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Speaking Sample') {
return const DuolingoSpeakingAssessment3Question();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Speaking') {
return const DuolingoSpeakingAssessment4Question();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Write About the Photo') {
return DuolingoWritingAssessment1Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Writing Sample') {
return DuolingoWritingAssessment2Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 1') {
return DuolingoWritingAssessment3Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 2') {
return DuolingoWritingAssessment4Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Listen and Type') {
return DuolingoListeningAssessment1Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Listening - Part 1') {
return DuolingoListeningAssessment2Question(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Listening - Part 2') {
return DuolingoListeningAssessment3Question(
assessmentController: assessmentController);
}
return Container();
}
Widget _buildAnswerScreen(DuolingoViewModel viewModel) {
if (viewModel.selectedAssessment['intro_title'] ==
'Speak About the Photo') {
return const DuolingoSpeakingAssessment1Answer();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Read, Then Speak') {
return const DuolingoSpeakingAssessment2Answer();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Speaking Sample') {
return const DuolingoSpeakingAssessment3Answer();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Speaking') {
return const DuolingoSpeakingAssessment4Answer();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Write About the Photo') {
return Container();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Writing Sample') {
return DuolingoWritingAssessment2Answer(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 1') {
return Container();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 2') {
return Container();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Listen and Type') {
return Container();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Listening - Part 1') {
return Container();
}
return Container();
}
Widget _buildReviewScreen(DuolingoViewModel viewModel) {
if (viewModel.selectedAssessment['intro_title'] ==
'Speak About the Photo') {
return const DuolingoSpeakingAssessment1Review();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Read, Then Speak') {
return const DuolingoSpeakingAssessment2Review();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Speaking Sample') {
return const DuolingoSpeakingAssessment3Review();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Speaking') {
return const DuolingoSpeakingAssessment4Review();
} else if (viewModel.selectedAssessment['intro_title'] ==
'Write About the Photo') {
return DuolingoWritingAssessment1Review(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Writing Sample') {
return DuolingoWritingAssessment2Review(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 1') {
return DuolingoWritingAssessment3Review(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Writing Part 2') {
return DuolingoWritingAssessment4Review(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Listen and Type') {
return DuolingoListeningAssessment1Review(
assessmentController: assessmentController);
} else if (viewModel.selectedAssessment['intro_title'] ==
'Interactive Listening - Part 1') {
return DuolingoListeningAssessment2Review(
assessmentController: assessmentController);
}
return Container();
}
@override @override
DuolingoViewModel viewModelBuilder(BuildContext context) => DuolingoViewModel viewModelBuilder(BuildContext context) =>
@ -198,40 +45,40 @@ class DuolingoView extends StackedView<DuolingoViewModel> with $DuolingoView {
index: viewModel.currentPage, children: _buildScreens(viewModel)); index: viewModel.currentPage, children: _buildScreens(viewModel));
List<Widget> _buildScreens(DuolingoViewModel viewModel) => [ List<Widget> _buildScreens(DuolingoViewModel viewModel) => [
_buildDuolingoAssessmentsScreen(), // _buildDuolingoAssessmentsScreen(),
_buildDuolingoIntroScreen(viewModel), // _buildDuolingoIntroScreen(viewModel),
_buildDuolingoQuestionScreen(viewModel), // _buildDuolingoQuestionScreen(viewModel),
_buildDuolingoAnswerScreen(viewModel), // _buildDuolingoAnswerScreen(viewModel),
_buildDuolingoReviewScreen(viewModel), // _buildDuolingoReviewScreen(viewModel),
_buildDuolingoRetakeScreen(viewModel), // _buildDuolingoRetakeScreen(viewModel),
_buildDuolingoFinishScreen(viewModel), // _buildDuolingoFinishScreen(viewModel),
]; ];
Widget _buildDuolingoAssessmentsScreen() => // Widget _buildDuolingoAssessmentsScreen() =>
const DuolingoAssessmentsScreens(); // const DuolingoAssessmentsScreens();
//
Widget _buildDuolingoIntroScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoIntroScreen(DuolingoViewModel viewModel) =>
DuolingoIntroScreen( // DuolingoIntroScreen(
type: viewModel.selectedAssessment['type'], // type: viewModel.selectedAssessment['type'],
title: viewModel.selectedAssessment['intro_title'], // title: viewModel.selectedAssessment['intro_title'],
subtitle: viewModel.selectedAssessment['intro_subtitle']); // subtitle: viewModel.selectedAssessment['intro_subtitle']);
//
Widget _buildDuolingoQuestionScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoQuestionScreen(DuolingoViewModel viewModel) =>
_buildQuestionScreen(viewModel); // _buildQuestionScreen(viewModel);
//
Widget _buildDuolingoAnswerScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoAnswerScreen(DuolingoViewModel viewModel) =>
_buildAnswerScreen(viewModel); // _buildAnswerScreen(viewModel);
//
Widget _buildDuolingoReviewScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoReviewScreen(DuolingoViewModel viewModel) =>
_buildReviewScreen(viewModel); // _buildReviewScreen(viewModel);
//
Widget _buildDuolingoRetakeScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoRetakeScreen(DuolingoViewModel viewModel) =>
DuolingoRetakeScreen( // DuolingoRetakeScreen(
title: viewModel.selectedAssessment['outro_title'], // title: viewModel.selectedAssessment['outro_title'],
subtitle: viewModel.selectedAssessment['outro_subtitle']); // subtitle: viewModel.selectedAssessment['outro_subtitle']);
//
Widget _buildDuolingoFinishScreen(DuolingoViewModel viewModel) => // Widget _buildDuolingoFinishScreen(DuolingoViewModel viewModel) =>
DuolingoFinishScreen( // DuolingoFinishScreen(
title: viewModel.selectedAssessment['outro_title'], // title: viewModel.selectedAssessment['outro_title'],
subtitle: viewModel.selectedAssessment['outro_subtitle']); // subtitle: viewModel.selectedAssessment['outro_subtitle']);
} }

View File

@ -1,107 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
class DuolingoFinishScreen extends ViewModelWidget<DuolingoViewModel> {
final String title;
final String subtitle;
const DuolingoFinishScreen(
{super.key, required this.title, required this.subtitle});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildSafeWrapper(viewModel),
);
Widget _buildSafeWrapper(DuolingoViewModel viewModel) =>
SafeArea(child: _buildScaffold(viewModel));
Widget _buildScaffold(DuolingoViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(DuolingoViewModel viewModel) => [
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody(DuolingoViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(DuolingoViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(DuolingoViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(DuolingoViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
];
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
onClose: () => viewModel.goTo(0),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/complete.svg',
);
Widget _buildTitle() => Text(
title,
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: () => viewModel.goTo(0),
foregroundColor: kcPrimaryColor,
);
}

View File

@ -1,125 +0,0 @@
import 'package:flutter/material.dart';
import 'package:iconsax/iconsax.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/duolingo_assessment_app_bar.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/wave_wrapper.dart';
class DuolingoIntroScreen extends ViewModelWidget<DuolingoViewModel> {
final String title;
final String subtitle;
final DuolingoAssessments type;
const DuolingoIntroScreen(
{super.key,
required this.type,
required this.title,
required this.subtitle});
IconData _getIcon() {
if (type == DuolingoAssessments.speaking) {
return Icons.waves;
} else if (type == DuolingoAssessments.writing) {
return Iconsax.pen_add;
} else if (type == DuolingoAssessments.listening) {
return Icons.hearing;
} else {
return Icons.book;
}
}
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
);
List<Widget> _buildSpeakingIndicatorChildren() => [
_buildSpeakingIconWrapper(),
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
];
Widget _buildTitle() => Text(
title,
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14DG400,
textAlign: TextAlign.center,
);
Widget _buildSpeakingIconWrapper() =>
WaveWrapper(height: 125, child: _buildSpeakingIcon());
Widget _buildSpeakingIcon() => Icon(
_getIcon(),
size: 30,
color: kcPrimaryColor,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
text: 'Start Preparation',
onTap: () => viewModel.goTo(2),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,150 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.form.dart';
import 'package:yimaru_app/ui/widgets/listenable_assessment_card.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../duolingo_viewmodel.dart';
class DuolingoListeningAssessment2Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
const DuolingoListeningAssessment2Question(
{super.key, required this.assessmentController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Listening Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceLarge,
_buildLabel('1. Focus of the conference:'),
verticalSpaceTiny,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
_buildAssessmentWrapper(viewModel),
verticalSpaceSmall,
_buildLabel('2. Number of presentations:'),
verticalSpaceTiny,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
_buildAssessmentWrapper(viewModel),
verticalSpaceSmall,
_buildLabel('3. Keynote speakers field:'),
verticalSpaceTiny,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
_buildAssessmentWrapper(viewModel),
];
Widget _buildTitle() => Text(
'Listen to the audio message and type your response.',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const ListenableAssessmentCard();
Widget _buildLabel(String question) => Text(
question,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
TextFormField(
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
class DuolingoRetakeScreen extends ViewModelWidget<DuolingoViewModel> {
final String title;
final String subtitle;
const DuolingoRetakeScreen(
{super.key, required this.title, required this.subtitle});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildSafeWrapper(viewModel),
);
Widget _buildSafeWrapper(DuolingoViewModel viewModel) =>
SafeArea(child: _buildScaffold(viewModel));
Widget _buildScaffold(DuolingoViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(DuolingoViewModel viewModel) => [
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody(DuolingoViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(DuolingoViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(DuolingoViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
Widget _buildUpperColumn(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(DuolingoViewModel viewModel) => [
verticalSpaceLarge,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
];
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
onClose: () => viewModel.goTo(0),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/complete.svg',
);
Widget _buildTitle() => Text(
title,
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
style: style14MG400,
textAlign: TextAlign.center,
);
Widget _buildLowerColumn(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(DuolingoViewModel viewModel) => [
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel)
];
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
safe: false,
borderRadius: 12,
text: 'Practice Again',
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(0),
backgroundColor: kcPrimaryColor,
);
Widget _buildSkipButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildSkipButton(viewModel),
);
Widget _buildSkipButton(DuolingoViewModel viewModel) => CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: () => viewModel.goTo(6),
foregroundColor: kcPrimaryColor,
);
}

View File

@ -1,123 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/countdown_timer.dart';
import 'package:yimaru_app/ui/widgets/speaking_indicator.dart';
import 'package:yimaru_app/ui/widgets/speaking_label.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment1Answer
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment1Answer({
super.key,
});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceSmall,
_buildCountdownWrapper(),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
);
List<Widget> _buildSpeakingIndicatorChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageContainer(),
verticalSpaceSmall,
_buildSpeakerLabel(),
verticalSpaceSmall,
_buildSpeakingIndicator()
];
Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildTitle() => Text(
'Speak about the photo below',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildImageContainer() => SizedBox(
width: 250,
height: 300,
child: _buildImageWrapper(),
);
Widget _buildImageWrapper() => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/image_1.png',
fit: BoxFit.fill,
width: double.maxFinite,
);
Widget _buildSpeakerLabel() => const SpeakingLabel();
Widget _buildSpeakingIndicator() => const SpeakingIndicator(
size: 25,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,142 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment1Question
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment1Question({
super.key,
});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
);
List<Widget> _buildSpeakingIndicatorChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildTimerStackWrapper(),
verticalSpaceSmall,
_buildSubtitle(),
verticalSpaceSmall,
_buildImageContainer(),
];
Widget _buildTitle() => Text(
'Prepare to speak about the photo',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildTimerStackWrapper() => SizedBox(
height: 50,
child: _buildTimerStack(),
);
Widget _buildTimerStack() => Stack(
alignment: Alignment.center,
children: _buildTimerStackChildren(),
);
List<Widget> _buildTimerStackChildren() =>
[_buildTimer(), _buildCountdownTime()];
Widget _buildTimer() => const CircularProgressIndicator(
value: 1.0,
strokeWidth: 5,
color: kcPrimaryColor,
padding: EdgeInsets.zero,
constraints: BoxConstraints(minWidth: 50, minHeight: 50),
);
Widget _buildCountdownTime() => Text(
'0:20',
style: style16P600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
'Prep time',
style: style16DG400,
textAlign: TextAlign.center,
);
Widget _buildImageContainer() => SizedBox(
width: 250,
height: 300,
child: _buildImageWrapper(),
);
Widget _buildImageWrapper() => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/image_1.png',
fit: BoxFit.fill,
width: double.maxFinite,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Start Speaking',
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(3),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,104 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/countdown_timer.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/speaking_assessment_review_section.dart';
import '../duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment1Review
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment1Review({
super.key,
});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildImageSectionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel)
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceSmall,
_buildCountdownWrapper(),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildImageSectionIndenter(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildImageSectionWrapper(viewModel),
);
Widget _buildImageSectionWrapper(DuolingoViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildImageSectionChildren(),
);
List<Widget> _buildImageSectionChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageContainer(),
];
Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildTitle() => Text(
'Speak about the photo below',
style: style18DG700,
textAlign: TextAlign.center,
);
Widget _buildImageContainer() => SizedBox(
width: 150,
height: 200,
child: _buildImageWrapper(),
);
Widget _buildImageWrapper() => ClipRRect(
borderRadius: BorderRadius.circular(5),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/image_1.png',
fit: BoxFit.fill,
width: double.maxFinite,
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
SpeakingAssessmentReviewSection(onTap: () => viewModel.goTo(5));
}

View File

@ -1,68 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/speaking_assessment_review_section.dart';
import '../../../widgets/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment3Review
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment3Review({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildQuestionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel)
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceSmall,
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionIndenter(DuolingoViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildQuestion(),
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
subtitle:
'What values of beliefs did you learn from living in your hometown? How will your hometown influence your future?',
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
SpeakingAssessmentReviewSection(onTap: () => viewModel.goTo(5));
}

View File

@ -1,14 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/failure/screens/first_failure_screen.dart'; import 'package:yimaru_app/ui/views/failure/screens/first_failure_screen.dart';
import 'package:yimaru_app/ui/views/failure/screens/second_failure_screen.dart'; import 'package:yimaru_app/ui/views/failure/screens/second_failure_screen.dart';
import 'package:yimaru_app/ui/views/failure/screens/third_failure_screen.dart'; import 'package:yimaru_app/ui/views/failure/screens/third_failure_screen.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import 'failure_viewmodel.dart'; import 'failure_viewmodel.dart';
class FailureView extends StackedView<FailureViewModel> { class FailureView extends StackedView<FailureViewModel> {

View File

@ -1,43 +1,40 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart';
import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
class FirstFailureScreen extends ViewModelWidget<FailureViewModel> { class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
final String label; final String label;
final GestureTapCallback onTap; final GestureTapCallback onTap;
const FirstFailureScreen({super.key,required this.onTap,required this.label}); const FirstFailureScreen(
{super.key, required this.onTap, required this.label});
@override @override
Widget build(BuildContext context, FailureViewModel viewModel) => Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(), body: _buildScaffoldPadding(),
); );
Widget _buildScaffoldPadding( ) => Padding( Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(), child: _buildScaffold(),
); );
Widget _buildScaffold( ) => Column( Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(), children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren( ) => List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()]; [_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column( Widget _buildUpperColumn() => Column(
@ -59,17 +56,17 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
height: 25, height: 25,
); );
Widget _buildLowerColumnWrapper( ) => Expanded( Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(), child: _buildLowerColumn(),
); );
Widget _buildLowerColumn( ) => Column( Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(), children: _buildLowerColumnChildren(),
); );
List<Widget> _buildLowerColumnChildren( ) => [ List<Widget> _buildLowerColumnChildren() => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildImageWrapper(), _buildImageWrapper(),
@ -110,16 +107,13 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
Widget _buildSafeWrapper( ) => Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align( Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(), child: _buildLoadingTextContainer(),
); );
Widget _buildLoadingTextContainer() => Padding( Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(), child: _buildLoadingTextWrapper(),
@ -139,8 +133,7 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
_buildRetryButtonWrapper() _buildRetryButtonWrapper()
]; ];
Widget _buildLoadingText() => Widget _buildLoadingText() => Text('$label...', style: style16W600);
Text('$label...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox( Widget _buildIndicatorWrapper() => SizedBox(
width: 16, width: 16,
@ -151,7 +144,6 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildRetryButtonWrapper() => GestureDetector( Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap, onTap: onTap,
child: _buildRetryButton(), child: _buildRetryButton(),

View File

@ -1,13 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
class SecondFailureScreen extends ViewModelWidget<FailureViewModel> { class SecondFailureScreen extends ViewModelWidget<FailureViewModel> {

View File

@ -1,13 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart'; import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> { class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
@ -21,23 +18,23 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
Widget build(BuildContext context, FailureViewModel viewModel) => Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor:kcWhite, backgroundColor: kcWhite,
body: _buildScaffoldPadding(), body: _buildScaffoldPadding(),
); );
Widget _buildScaffoldPadding( ) => Padding( Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(), child: _buildScaffold(),
); );
Widget _buildScaffold( ) => Column( Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(), children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren( ) => List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()]; [_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column( Widget _buildUpperColumn() => Column(
@ -59,17 +56,17 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
height: 25, height: 25,
); );
Widget _buildLowerColumnWrapper( ) => Expanded( Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(), child: _buildLowerColumn(),
); );
Widget _buildLowerColumn( ) => Column( Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(), children: _buildLowerColumnChildren(),
); );
List<Widget> _buildLowerColumnChildren( ) => [ List<Widget> _buildLowerColumnChildren() => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildImageWrapper(), _buildImageWrapper(),
@ -77,7 +74,6 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
_buildSafeWrapper() _buildSafeWrapper()
]; ];
Widget _buildTitle() => Text.rich( Widget _buildTitle() => Text.rich(
TextSpan( TextSpan(
text: 'እንግሊዝኛ\n', text: 'እንግሊዝኛ\n',
@ -111,16 +107,13 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
Widget _buildSafeWrapper( ) => Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align( Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(), child: _buildLoadingTextContainer(),
); );
Widget _buildLoadingTextContainer() => Padding( Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(), child: _buildLoadingTextWrapper(),
@ -136,11 +129,11 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
_buildLoadingText(), _buildLoadingText(),
horizontalSpaceSmall, horizontalSpaceSmall,
_buildIndicatorWrapper(), _buildIndicatorWrapper(),
horizontalSpaceSmall,_buildRetryButtonWrapper() horizontalSpaceSmall,
_buildRetryButtonWrapper()
]; ];
Widget _buildLoadingText() => Widget _buildLoadingText() => Text('$label...', style: style16P600);
Text('$label...', style: style16P600);
Widget _buildIndicatorWrapper() => SizedBox( Widget _buildIndicatorWrapper() => SizedBox(
width: 16, width: 16,
@ -162,4 +155,3 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
fontStyle: FontStyle.italic, decoration: TextDecoration.underline), fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
); );
} }

View File

@ -5,12 +5,10 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/views/course/course_view.dart'; import 'package:yimaru_app/ui/views/course/course_view.dart';
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../widgets/coming_soon.dart';
import 'home_viewmodel.dart'; import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> { class HomeView extends StackedView<HomeViewModel> {

View File

@ -15,7 +15,6 @@ class HomeViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
final _statusChecker = locator<StatusCheckerService>(); final _statusChecker = locator<StatusCheckerService>();
final _bottomSheetService = locator<BottomSheetService>(); final _bottomSheetService = locator<BottomSheetService>();
final _inAppUpdateService = locator<InAppUpdateService>(); final _inAppUpdateService = locator<InAppUpdateService>();
@ -26,7 +25,7 @@ class HomeViewModel extends ReactiveViewModel {
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_authenticationService,_inAppNotificationService]; [_authenticationService, _inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;
@ -66,7 +65,6 @@ class HomeViewModel extends ReactiveViewModel {
} }
} }
// Unread notifications // Unread notifications
Future<void> getUnreadNotifications() async { Future<void> getUnreadNotifications() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {

View File

@ -53,5 +53,4 @@ class LandingView extends StackedView<LandingViewModel> {
Widget _buildSecondLanding() => const SecondLandingScreen(); Widget _buildSecondLanding() => const SecondLandingScreen();
Widget _buildThirdLanding() => const ThirdLandingScreen(); Widget _buildThirdLanding() => const ThirdLandingScreen();
} }

View File

@ -26,7 +26,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
{required BuildContext context, {required BuildContext context,
required LearnModule module, required LearnModule module,
required LearnModuleViewModel viewModel}) async { required LearnModuleViewModel viewModel}) async {
if (module.access?.isCompleted ?? false ) { if (module.access?.isCompleted ?? false) {
await viewModel.navigateToLearnPractice( await viewModel.navigateToLearnPractice(
id: module.id ?? 0, module: module.name ?? ''); id: module.id ?? 0, module: module.name ?? '');
} else { } else {

View File

@ -2,7 +2,7 @@ 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/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_finish_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_finish_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_loading_screen.dart'; import 'package:yimaru_app/ui/widgets/practice_loading_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_description_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_description_screen.dart';
@ -12,7 +12,7 @@ import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_intro_
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../widgets/cancel_learn_practice_sheet.dart'; import '../../widgets/cancel_practice_sheet.dart';
import 'learn_practice_viewmodel.dart'; import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> { class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
@ -80,8 +80,7 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
}, },
child: _buildScaffoldWrapper(viewModel)); child: _buildScaffoldWrapper(viewModel));
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',
@ -101,7 +100,7 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
: _buildBody(viewModel); : _buildBody(viewModel);
Widget _buildPageLoadingIndicator(LearnPracticeViewModel viewModel) => Widget _buildPageLoadingIndicator(LearnPracticeViewModel viewModel) =>
LearnLoadingScreen( PracticeLoadingScreen(
isLoading: viewModel.busy(StateObjects.learnPractice), isLoading: viewModel.busy(StateObjects.learnPractice),
onTap: () async => onTap: () async =>
await viewModel.getLearnPractices(id: id, practice: practice), await viewModel.getLearnPractices(id: id, practice: practice),

View File

@ -5,7 +5,7 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:waveform_recorder/waveform_recorder.dart'; import 'package:waveform_recorder/waveform_recorder.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/cancel_learn_practice_sheet.dart'; import 'package:yimaru_app/ui/widgets/cancel_practice_sheet.dart';
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/wave_wrapper.dart'; import 'package:yimaru_app/ui/widgets/wave_wrapper.dart';
@ -365,8 +365,7 @@ class InteractLearnPracticeScreen
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -7,7 +7,7 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
@ -104,8 +104,7 @@ class LearnPracticeAppreciationScreen
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -8,7 +8,7 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/translations/locale_keys.g.dart'; import '../../../common/translations/locale_keys.g.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
@ -87,8 +87,7 @@ class LearnPracticeDescriptionScreen
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -7,7 +7,7 @@ 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/enmus.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart'; import '../../../widgets/speaking_partner_image.dart';
@ -106,8 +106,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -9,7 +9,7 @@ import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/page_loading_indicator.dart'; import '../../../widgets/page_loading_indicator.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
@ -120,8 +120,7 @@ class LearnPracticeResultScreen
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -8,7 +8,7 @@ import 'package:yimaru_app/ui/widgets/custom_column_button.dart';
import '../../../../models/learn_question.dart'; import '../../../../models/learn_question.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> { class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
@ -111,8 +111,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
title: title:
'${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})'); '${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})');
Widget _buildSheet(LearnPracticeViewModel viewModel) => Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
CancelLearnPracticeSheet(
onClose: viewModel.pop, onClose: viewModel.pop,
onContinue: viewModel.pop, onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '', user: viewModel.user?.firstName ?? '',

View File

@ -26,7 +26,7 @@ class LearnProgramViewModel extends ReactiveViewModel {
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService,_inAppNotificationService]; [_learnService, _authenticationService, _inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;

View File

@ -75,7 +75,6 @@ class NotificationView extends StackedView<NotificationViewModel> {
title: LocaleKeys.notifications.tr(), title: LocaleKeys.notifications.tr(),
); );
Widget _buildNotificationsColumnWrapper(NotificationViewModel viewModel) => Widget _buildNotificationsColumnWrapper(NotificationViewModel viewModel) =>
Expanded(child: _buildNotificationsColumnScrollView(viewModel)); Expanded(child: _buildNotificationsColumnScrollView(viewModel));
@ -107,6 +106,4 @@ class NotificationView extends StackedView<NotificationViewModel> {
required InAppNotification notification, required InAppNotification notification,
}) => }) =>
NotificationCard(notification: notification); NotificationCard(notification: notification);
} }

View File

@ -10,7 +10,6 @@ import '../../../app/app.locator.dart';
import '../../../models/user.dart'; import '../../../models/user.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/in_app_notification_service.dart'; import '../../../services/in_app_notification_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
@ -32,7 +31,7 @@ class ProfileViewModel extends ReactiveViewModel {
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_authenticationService,_inAppNotificationService]; [_authenticationService, _inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;

View File

@ -5,7 +5,6 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart'; import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart'; import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
@ -13,29 +12,29 @@ import '../../../widgets/custom_circular_progress_indicator.dart';
class FirstStartupScreen extends ViewModelWidget<StartupViewModel> { class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label; final String? label;
const FirstStartupScreen({super.key,this.label}); const FirstStartupScreen({super.key, this.label});
@override @override
Widget build(BuildContext context, StartupViewModel viewModel) => Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(), body: _buildScaffoldPadding(),
); );
Widget _buildScaffoldPadding( ) => Padding( Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(), child: _buildScaffold(),
); );
Widget _buildScaffold( ) => Column( Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(), children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren( ) => List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()]; [_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column( Widget _buildUpperColumn() => Column(
@ -57,17 +56,17 @@ class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
height: 25, height: 25,
); );
Widget _buildLowerColumnWrapper( ) => Expanded( Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(), child: _buildLowerColumn(),
); );
Widget _buildLowerColumn( ) => Column( Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(), children: _buildLowerColumnChildren(),
); );
List<Widget> _buildLowerColumnChildren( ) => [ List<Widget> _buildLowerColumnChildren() => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildImageWrapper(), _buildImageWrapper(),
@ -108,16 +107,13 @@ class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
Widget _buildSafeWrapper( ) => Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align( Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(), child: _buildLoadingTextContainer(),
); );
Widget _buildLoadingTextContainer() => Padding( Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(), child: _buildLoadingTextWrapper(),
@ -146,6 +142,4 @@ class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcWhite);
} }

View File

@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart'; import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
@ -13,29 +12,29 @@ import '../startup_viewmodel.dart';
class SecondStartupScreen extends ViewModelWidget<StartupViewModel> { class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label; final String? label;
const SecondStartupScreen({super.key,this.label}); const SecondStartupScreen({super.key, this.label});
@override @override
Widget build(BuildContext context, StartupViewModel viewModel) => Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: Colors.amber, backgroundColor: Colors.amber,
body: _buildScaffoldPadding(), body: _buildScaffoldPadding(),
); );
Widget _buildScaffoldPadding( ) => Padding( Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(), child: _buildScaffold(),
); );
Widget _buildScaffold( ) => Column( Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(), children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren( ) => List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()]; [_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column( Widget _buildUpperColumn() => Column(
@ -57,17 +56,17 @@ class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
height: 25, height: 25,
); );
Widget _buildLowerColumnWrapper( ) => Expanded( Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(), child: _buildLowerColumn(),
); );
Widget _buildLowerColumn( ) => Column( Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(), children: _buildLowerColumnChildren(),
); );
List<Widget> _buildLowerColumnChildren( ) => [ List<Widget> _buildLowerColumnChildren() => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildImageWrapper(), _buildImageWrapper(),
@ -75,7 +74,6 @@ class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
_buildSafeWrapper() _buildSafeWrapper()
]; ];
Widget _buildTitle() => Text.rich( Widget _buildTitle() => Text.rich(
TextSpan( TextSpan(
text: 'እንግሊዝኛ\n', text: 'እንግሊዝኛ\n',
@ -109,16 +107,13 @@ class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
Widget _buildSafeWrapper( ) => Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align( Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(), child: _buildLoadingTextContainer(),
); );
Widget _buildLoadingTextContainer() => Padding( Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(), child: _buildLoadingTextWrapper(),
@ -147,7 +142,4 @@ class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcPrimaryColor); const CustomCircularProgressIndicator(color: kcPrimaryColor);
} }

View File

@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart'; import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart'; import '../../../widgets/custom_circular_progress_indicator.dart';
@ -13,29 +12,29 @@ import '../startup_viewmodel.dart';
class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> { class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label; final String? label;
const ThirdStartupScreen({super.key,this.label}); const ThirdStartupScreen({super.key, this.label});
@override @override
Widget build(BuildContext context, StartupViewModel viewModel) => Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor:kcWhite, backgroundColor: kcWhite,
body: _buildScaffoldPadding(), body: _buildScaffoldPadding(),
); );
Widget _buildScaffoldPadding( ) => Padding( Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(), child: _buildScaffold(),
); );
Widget _buildScaffold( ) => Column( Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(), children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren( ) => List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()]; [_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column( Widget _buildUpperColumn() => Column(
@ -57,17 +56,17 @@ class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
height: 25, height: 25,
); );
Widget _buildLowerColumnWrapper( ) => Expanded( Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(), child: _buildLowerColumn(),
); );
Widget _buildLowerColumn( ) => Column( Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(), children: _buildLowerColumnChildren(),
); );
List<Widget> _buildLowerColumnChildren( ) => [ List<Widget> _buildLowerColumnChildren() => [
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildImageWrapper(), _buildImageWrapper(),
@ -75,7 +74,6 @@ class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
_buildSafeWrapper() _buildSafeWrapper()
]; ];
Widget _buildTitle() => Text.rich( Widget _buildTitle() => Text.rich(
TextSpan( TextSpan(
text: 'እንግሊዝኛ\n', text: 'እንግሊዝኛ\n',
@ -109,16 +107,13 @@ class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
Widget _buildSafeWrapper( ) => Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align( Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(), child: _buildLoadingTextContainer(),
); );
Widget _buildLoadingTextContainer() => Padding( Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50), padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(), child: _buildLoadingTextWrapper(),
@ -147,7 +142,4 @@ class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcPrimaryColor); const CustomCircularProgressIndicator(color: kcPrimaryColor);
} }

View File

@ -1,18 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/startup/screens/first_startup_screen.dart'; import 'package:yimaru_app/ui/views/startup/screens/first_startup_screen.dart';
import 'package:yimaru_app/ui/views/startup/screens/second_startup_screen.dart'; import 'package:yimaru_app/ui/views/startup/screens/second_startup_screen.dart';
import 'package:yimaru_app/ui/views/startup/screens/third_startup_screen.dart'; import 'package:yimaru_app/ui/views/startup/screens/third_startup_screen.dart';
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/translations/locale_keys.g.dart';
import 'startup_viewmodel.dart'; import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> { class StartupView extends StackedView<StartupViewModel> {

View File

@ -8,7 +8,6 @@ import '../../../app/app.router.dart';
import '../../../models/user.dart'; import '../../../models/user.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/image_downloader_service.dart'; import '../../../services/image_downloader_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/localization_service.dart'; import '../../../services/localization_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
@ -29,7 +28,6 @@ class StartupViewModel extends ReactiveViewModel {
final _imageDownloaderService = locator<ImageDownloaderService>(); final _imageDownloaderService = locator<ImageDownloaderService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_onboardingService, _authenticationService]; [_onboardingService, _authenticationService];
@ -144,6 +142,4 @@ class StartupViewModel extends ReactiveViewModel {
await replaceWithFailure(); await replaceWithFailure();
} }
} }
} }

View File

@ -8,13 +8,13 @@ import '../common/ui_helpers.dart';
import 'custom_bottom_sheet.dart'; import 'custom_bottom_sheet.dart';
import 'custom_elevated_button.dart'; import 'custom_elevated_button.dart';
class CancelLearnPracticeSheet extends StatelessWidget { class CancelPracticeSheet extends StatelessWidget {
final String user; final String user;
final GestureTapCallback? onClose; final GestureTapCallback? onClose;
final GestureTapCallback? onCancel; final GestureTapCallback? onCancel;
final GestureTapCallback? onContinue; final GestureTapCallback? onContinue;
const CancelLearnPracticeSheet( const CancelPracticeSheet(
{super.key, {super.key,
this.onClose, this.onClose,
this.onCancel, this.onCancel,

View File

@ -1,40 +1,76 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timer_countdown/flutter_timer_countdown.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/views/course_practice/course_practice_viewmodel.dart';
import '../common/helper_functions.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
class CountdownTimer extends StatelessWidget { class CountdownTimer extends ViewModelWidget<CoursePracticeViewModel> {
const CountdownTimer({super.key}); final String? time;
const CountdownTimer({super.key, this.time});
Future<void> _stopRecording(CoursePracticeViewModel viewModel)async{
await viewModel.stopRecording();
viewModel.setNextButton();
}
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container( Widget _buildContainer(CoursePracticeViewModel viewModel) => Container(
width: 100, width: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
color: kcPrimaryColor.withValues(alpha: 0.1), color: kcPrimaryColor.withValues(alpha: 0.1),
), ),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: _buildRow(), child: _buildRow(viewModel),
); );
Widget _buildRow() => Row( Widget _buildRow(CoursePracticeViewModel viewModel) => Row(
children: [ children: [
_buildClockIcon(), _buildClockIcon(),
horizontalSpaceTiny, horizontalSpaceTiny,
_buildCountdownTime(), _buildCountdownState(viewModel),
], ],
); );
Widget _buildCountdownTime() => Text(
'0:20', Widget _buildCountdownState(CoursePracticeViewModel viewModel) => time == null ? _buildCountdownTime(viewModel):_buildCountdownText();
Widget _buildCountdownText() => Text(
time ?? '',
style: style16P600, style: style16P600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildCountdownTime(CoursePracticeViewModel viewModel) =>
TimerCountdown(
spacerWidth: 0,
enableDescriptions: false,
timeTextStyle: style14P600,
colonsTextStyle: style14P400,
format: CountDownTimerFormat.minutesSeconds,
onEnd:()async => await _stopRecording(viewModel) ,
endTime: DateTime.now().add(
Duration(
minutes: getMinutes(
viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.last.value['seconds'],
),
seconds: getSeconds(
viewModel.questions[viewModel.currentQuestion].dynamicPayload
?.stimulus?.last.value['seconds'],
),
),
),
);
Widget _buildClockIcon() => const Icon( Widget _buildClockIcon() => const Icon(
Icons.timer_outlined, Icons.timer_outlined,
color: kcPrimaryColor, color: kcPrimaryColor,
); );
} }

View File

@ -191,14 +191,14 @@ class CourseModuleTileLarge extends ViewModelWidget<CourseModuleViewModel> {
shrinkWrap: true, shrinkWrap: true,
itemCount: lessons.length, itemCount: lessons.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildCourseModuleCard( itemBuilder: (context, index) => _buildCourseLessonTile(
lesson: lessons[index], lesson: lessons[index],
onVideoTap: () async => onVideoTap: () async =>
await viewModel.navigateToCourseLessonDetail(lessons[index]), await viewModel.navigateToCourseLessonDetail(lessons[index]),
onPracticeTap: () {}), onPracticeTap: ()async=>await viewModel.navigateToCoursePractice(lessons[index].id ?? 0)),
); );
Widget _buildCourseModuleCard({ Widget _buildCourseLessonTile({
required CourseLesson lesson, required CourseLesson lesson,
required GestureTapCallback onVideoTap, required GestureTapCallback onVideoTap,
required GestureTapCallback onPracticeTap, required GestureTapCallback onPracticeTap,

View File

@ -1,56 +1,84 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/course_practice/course_practice_viewmodel.dart';
class CustomResponseCard extends StatelessWidget { import '../common/enmus.dart';
import 'custom_circular_progress_indicator.dart';
class CustomResponseCard extends ViewModelWidget<CoursePracticeViewModel> {
final Voice voice;
final String title; final String title;
final String subtitle;
const CustomResponseCard( const CustomResponseCard(
{super.key, required this.title, required this.subtitle}); {super.key,
required this.voice,
required this.title,
});
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container( Widget _buildContainer(CoursePracticeViewModel viewModel) => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: kcWhite, color: kcWhite,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
child: _buildRow(), child: _buildRow(viewModel),
); );
Widget _buildRow() => Row( Widget _buildRow(CoursePracticeViewModel viewModel) => Row(
children: [_buildPlayButton(), _buildColumnWrapper()], children: [_buildPlayButton(viewModel), _buildColumnWrapper()],
); );
Widget _buildPlayButton() => ElevatedButton( Widget _buildPlayButton(CoursePracticeViewModel viewModel) => ElevatedButton(
onPressed: () {},
style: const ButtonStyle( style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()), shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(5)), padding: WidgetStatePropertyAll(EdgeInsets.all(5)),
shadowColor: WidgetStatePropertyAll(kcPrimaryColor), shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
), ),
child: _buildPlayIcon(), onPressed: () async =>
viewModel.player.state == PlayerState.playing
? await viewModel.pauseAudio()
: await viewModel.playResult( voice),
child: _buildButtonState(viewModel),
); );
Widget _buildButtonState(CoursePracticeViewModel viewModel) =>
viewModel.playing == voice
? viewModel.busy(StateObjects.coursePracticeReview)
? _buildProgressIndicatorWrapper()
: viewModel.player.state == PlayerState.playing
? _buildPauseIcon()
: _buildPlayIcon()
: _buildPlayIcon();
Widget _buildProgressIndicatorWrapper() => Center(
child: _buildProgressIndicator(),
);
Widget _buildProgressIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildPlayIcon() => const Icon( Widget _buildPlayIcon() => const Icon(
Icons.play_arrow_rounded, Icons.play_arrow_rounded,
size: 25, size: 25,
color: kcWhite, color: kcWhite,
); );
Widget _buildColumnWrapper() => Expanded(child: _buildColumn()); Widget _buildPauseIcon() => const Icon(
Icons.pause,
Widget _buildColumn() => Column( size: 25,
mainAxisSize: MainAxisSize.min, color: kcWhite,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
); );
List<Widget> _buildColumnChildren() => [_buildTitle(), _buildSubtitle()]; Widget _buildColumnWrapper() => Expanded(child: _buildTitle());
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
title, title,
@ -58,11 +86,4 @@ class CustomResponseCard extends StatelessWidget {
softWrap: false, softWrap: false,
style: style12RP600, style: style12RP600,
); );
Widget _buildSubtitle() => Text(
subtitle,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
} }

View File

@ -1,11 +1,11 @@
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';
class DuolingoAssessmentAppBar extends StatelessWidget { class DuolingoPracticeAppBar extends StatelessWidget {
final String? title; final String? title;
final GestureTapCallback? onClose; final GestureTapCallback? onClose;
const DuolingoAssessmentAppBar({super.key, this.onClose, this.title}); const DuolingoPracticeAppBar({super.key, this.onClose, this.title});
@override @override
Widget build(BuildContext context) => _buildAppBar(); Widget build(BuildContext context) => _buildAppBar();

View File

@ -4,11 +4,11 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart'; import 'custom_elevated_button.dart';
class DuolingoAssessmentCard extends StatelessWidget { class DuolingoPracticeCard extends StatelessWidget {
final String title; final String title;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const DuolingoAssessmentCard({super.key, this.onTap, required this.title}); const DuolingoPracticeCard({super.key, this.onTap, required this.title});
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();

View File

@ -2,11 +2,11 @@ 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/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
class DuolingoAssessmentQuestionCard extends StatelessWidget { class DuolingoPracticeQuestionCard extends StatelessWidget {
final String? title; final String? title;
final String? subtitle; final String? subtitle;
const DuolingoAssessmentQuestionCard({super.key, this.title, this.subtitle}); const DuolingoPracticeQuestionCard({super.key, this.title, this.subtitle});
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();

View File

@ -4,9 +4,9 @@ import '../common/app_colors.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart'; import 'custom_elevated_button.dart';
class DuolingoAssessmentReviewSection extends StatelessWidget { class DuolingoPracticeReviewSection extends StatelessWidget {
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const DuolingoAssessmentReviewSection({super.key, this.onTap}); const DuolingoPracticeReviewSection({super.key, this.onTap});
@override @override
Widget build(BuildContext context) => _buildReviewContainer(); Widget build(BuildContext context) => _buildReviewContainer();

View File

@ -43,7 +43,7 @@ class LearnPracticeAnswerCard extends ViewModelWidget<LearnPracticeViewModel> {
shadowColor: WidgetStatePropertyAll(kcPrimaryColor), shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
), ),
onPressed: () async => viewModel.busyObject == answer['busy_object'] && onPressed: () async =>
viewModel.player.state == PlayerState.playing viewModel.player.state == PlayerState.playing
? await viewModel.pauseAudio() ? await viewModel.pauseAudio()
: await viewModel.playResult(answer: answer, voice: voice), : await viewModel.playResult(answer: answer, voice: voice),
@ -51,7 +51,7 @@ class LearnPracticeAnswerCard extends ViewModelWidget<LearnPracticeViewModel> {
); );
Widget _buildButtonState(LearnPracticeViewModel viewModel) => Widget _buildButtonState(LearnPracticeViewModel viewModel) =>
viewModel.busyObject == answer['busy_object'] &&
viewModel.playing == voice viewModel.playing == voice
? viewModel.busy(answer['busy_object']) ? viewModel.busy(answer['busy_object'])
? _buildProgressIndicatorWrapper() ? _buildProgressIndicatorWrapper()

View File

@ -5,8 +5,8 @@ import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
class ListenableAssessmentCard extends StatelessWidget { class ListenablePracticeCard extends StatelessWidget {
const ListenableAssessmentCard({super.key}); const ListenablePracticeCard({super.key});
@override @override
Widget build(BuildContext context) => _buildRow(); Widget build(BuildContext context) => _buildRow();

View File

@ -11,13 +11,11 @@ class NotificationCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container( Widget _buildContainer() => Container(
height: 100, height: 100,
width: double.maxFinite, width: double.maxFinite,
margin: const EdgeInsets.symmetric(horizontal: 15), margin: const EdgeInsets.symmetric(horizontal: 15),
padding: const EdgeInsets.symmetric(horizontal: 15,vertical: 15), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
color: (notification.isRead ?? false) color: (notification.isRead ?? false)
@ -37,11 +35,8 @@ class NotificationCard extends StatelessWidget {
children: _buildRowChildren(), children: _buildRowChildren(),
); );
List<Widget> _buildRowChildren()=> [ List<Widget> _buildRowChildren() =>
_buildIcon(), [_buildIcon(), horizontalSpaceSmall, _buildColumnWrapper()];
horizontalSpaceSmall,
_buildColumnWrapper()
];
Widget _buildIcon() => const Icon( Widget _buildIcon() => const Icon(
Icons.notifications_none, Icons.notifications_none,
@ -49,15 +44,14 @@ class NotificationCard extends StatelessWidget {
color: kcMediumGrey, color: kcMediumGrey,
); );
Widget _buildColumnWrapper()=> Expanded(child: _buildColumn()); Widget _buildColumnWrapper() => Expanded(child: _buildColumn());
Widget _buildColumn() => Column( Widget _buildColumn() => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(), children: _buildColumnChildren(),
); );
List<Widget> _buildColumnChildren() => List<Widget> _buildColumnChildren() => [_buildTitle(), _buildSubtitle()];
[ _buildTitle(), _buildSubtitle()];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
notification.payload?.headline ?? '', notification.payload?.headline ?? '',

View File

@ -1,30 +1,29 @@
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:badges/badges.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
class NotificationIcon extends StatelessWidget { class NotificationIcon extends StatelessWidget {
final String count; final String count;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const NotificationIcon({super.key,this.onTap,required this.count}); const NotificationIcon({super.key, this.onTap, required this.count});
@override @override
Widget build(BuildContext context) => _buildNotificationIconWrapper(); Widget build(BuildContext context) => _buildNotificationIconWrapper();
Widget _buildNotificationIconWrapper() => Align( Widget _buildNotificationIconWrapper() => Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight, child: _buildNotificationButton());
child: _buildNotificationButton());
Widget _buildNotificationButton() => Widget _buildNotificationButton() => GestureDetector(
GestureDetector(
onTap: onTap, onTap: onTap,
child: _buildNotificationBadge(), child: _buildNotificationBadge(),
); );
Widget _buildNotificationBadge()=> badges.Badge( Widget _buildNotificationBadge() => badges.Badge(
badgeContent: Text(count,style: style12W600,), badgeContent: Text(
count,
style: style12W600,
),
child: _buildNotificationIcon(), child: _buildNotificationIcon(),
); );
Widget _buildNotificationIcon() => const Icon( Widget _buildNotificationIcon() => const Icon(

View File

@ -4,17 +4,17 @@ import 'package:yimaru_app/ui/widgets/no_data_indicator.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart'; import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
import '../../../common/app_colors.dart'; import '../common/app_colors.dart';
import '../../../common/translations/locale_keys.g.dart'; import '../common/translations/locale_keys.g.dart';
import '../../../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
class LearnLoadingScreen extends StatelessWidget { class PracticeLoadingScreen extends StatelessWidget {
final bool isEmpty; final bool isEmpty;
final bool isLoading; final bool isLoading;
final GestureTapCallback? onPop; final GestureTapCallback? onPop;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const LearnLoadingScreen( const PracticeLoadingScreen(
{super.key, {super.key,
this.onTap, this.onTap,
this.onPop, this.onPop,

View File

@ -1,56 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_response_card.dart';
class PracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
final Map<String, dynamic> data;
const PracticeResultCard({super.key, required this.data});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildColumnWrapper();
Widget _buildColumnWrapper() => SizedBox(
height: 100,
width: double.maxFinite,
child: _buildColumn(),
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[_buildQuestion(), verticalSpaceSmall, _buildRow()];
Widget _buildQuestion() => Text(
data['question_text'],
style: style14DG400,
);
Widget _buildRow() => Row(
children: _buildRowChildren(),
);
List<Widget> _buildRowChildren() => [
_buildSampleResponseWrapper(),
horizontalSpaceSmall,
_buildActualResponseWrapper()
];
Widget _buildSampleResponseWrapper() =>
Expanded(child: _buildSampleResponse());
Widget _buildSampleResponse() =>
const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54');
Widget _buildActualResponseWrapper() =>
Expanded(child: _buildActualResponse());
Widget _buildActualResponse() =>
const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54');
}

View File

@ -16,7 +16,11 @@ class ProfileAppBar extends StatelessWidget {
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const ProfileAppBar( const ProfileAppBar(
{super.key, this.onTap, required this.name,required this.unreadCount, required this.profileImage}); {super.key,
this.onTap,
required this.name,
required this.unreadCount,
required this.profileImage});
@override @override
Widget build(BuildContext context) => _buildStack(); Widget build(BuildContext context) => _buildStack();
@ -94,8 +98,8 @@ class ProfileAppBar extends StatelessWidget {
style: style14DG400, style: style14DG400,
); );
Widget _buildNotificationIconWrapper() => NotificationIcon(
Widget _buildNotificationIconWrapper() => count: unreadCount,
NotificationIcon(count: unreadCount,onTap:onTap ,) onTap: onTap,
; );
} }

Some files were not shown because too many files have changed in this diff Show More