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",
"total_practices": "Total Practices",
"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/services/in_app_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
@StackedApp(
@ -100,6 +101,7 @@ import 'package:yimaru_app/services/push_notification_service.dart';
MaterialRoute(page: LearnCourseView),
MaterialRoute(page: PaymentView),
MaterialRoute(page: NotificationView),
MaterialRoute(page: CoursePracticeView),
// @stacked-route
],
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 '../models/course_module.dart';
import '../models/course_practice.dart';
import '../models/course_question.dart';
import '../models/course_unit.dart';
import '../models/field_option.dart';
import '../models/learn_course.dart';
@ -903,7 +905,7 @@ class ApiService {
print('Here');
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) {
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
Future<Map<String, dynamic>> checkUpdate(Map<String, dynamic> data) async {
try {

View File

@ -6,6 +6,8 @@ import '../models/course_catalog.dart';
import '../models/course_lesson.dart';
import '../models/course_module.dart';
import '../models/course_unit.dart';
import '../models/refresh_object.dart';
import '../ui/common/enmus.dart';
class CourseService with ListenableServiceMixin {
// Dependency injection
@ -76,4 +78,16 @@ class CourseService with ListenableServiceMixin {
_lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
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,130 +7,132 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await locator<PushNotificationService>().setupFlutterNotifications();
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 {
// Initialize FCM token
await updateFCMToken();
final _localNotifications = FlutterLocalNotificationsPlugin();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
Future<void> initialize() async {
// Initialize FCM token
await updateFCMToken();
// Request permission
await _requestPermission();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// setup message handle
await _setupMessageHandler();
// Request permission
await _requestPermission();
// Subscribe to all devices
subscribeToTopic('yimaru');
}
// setup message handle
await _setupMessageHandler();
Future<void> _requestPermission() async {
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
carPlay: false,
provisional: false,
announcement: false,
criticalAlert: false);
}
Future<void> setupFlutterNotifications() async {
if (_isFlutterLocalNotificationInitialized) {
return;
// Subscribe to all devices
subscribeToTopic('yimaru');
}
// Android setup
const channel = AndroidNotificationChannel(
'yimaru', // id
'Yimaru', // title
importance: Importance.high,
);
Future<void> _requestPermission() async {
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
carPlay: false,
provisional: false,
announcement: false,
criticalAlert: false);
}
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
Future<void> setupFlutterNotifications() async {
if (_isFlutterLocalNotificationInitialized) {
return;
}
const initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// IOS setup
const initializationSettingsDarwin = DarwinInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin);
// Flutter notification setup
await _localNotifications.initialize(
settings: initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
},
);
_isFlutterLocalNotificationInitialized = true;
}
Future<void> showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
await _localNotifications.show(
id: notification.hashCode,
title: notification.title,
body: notification.body,
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails('yimaru', 'Yimaru',
enableVibration: true,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
importance: Importance.high),
iOS: DarwinNotificationDetails(
presentAlert: true, presentBadge: true, presentSound: true)),
// Android setup
const channel = AndroidNotificationChannel(
'yimaru', // id
'Yimaru', // title
importance: Importance.high,
);
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
const initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// IOS setup
const initializationSettingsDarwin = DarwinInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin);
// Flutter notification setup
await _localNotifications.initialize(
settings: initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
},
);
_isFlutterLocalNotificationInitialized = true;
}
Future<void> showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
await _localNotifications.show(
id: notification.hashCode,
title: notification.title,
body: notification.body,
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails('yimaru', 'Yimaru',
enableVibration: true,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
importance: Importance.high),
iOS: DarwinNotificationDetails(
presentAlert: true, presentBadge: true, presentSound: true)),
);
}
}
Future<void> _setupMessageHandler() async {
// Foreground message
FirebaseMessaging.onMessage
.listen((RemoteMessage message) => showNotification(message));
// Background message
FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage);
// Opened app
final initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
_handleBackgroundMessage(initialMessage);
}
}
void _handleBackgroundMessage(RemoteMessage message) {
if (message.data['type'] == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
}
Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
Future<void> updateFCMToken() async {
// print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken);
});
}
}
Future<void> _setupMessageHandler() async {
// Foreground message
FirebaseMessaging.onMessage
.listen((RemoteMessage message) => showNotification(message));
// Background message
FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage);
// Opened app
final initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
_handleBackgroundMessage(initialMessage);
}
}
void _handleBackgroundMessage(RemoteMessage message) {
if (message.data['type'] == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
}
Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
Future<void> updateFCMToken() async {
// print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken);
});
}
}

View File

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

View File

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

View File

@ -3,6 +3,24 @@ import 'dart:ui';
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
Map<String, String> splitFullName(String fullName) {
final parts = fullName.trim().split(RegExp(r'\s+'));

View File

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

View File

@ -51,8 +51,8 @@ class CourseView extends StackedView<CourseViewModel> {
Widget _buildAppBar(CourseViewModel viewModel) => ProfileAppBar(
name: viewModel.user?.firstName,
profileImage: viewModel.user?.profilePicture,
unreadCount: viewModel.unreadCount.toString(),
onTap: () async => await viewModel.navigateToNotification(),
unreadCount: viewModel.unreadCount.toString(),
onTap: () async => await viewModel.navigateToNotification(),
);
Widget _buildCategoryColumnWrapper(CourseViewModel viewModel) =>

View File

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

View File

@ -30,6 +30,8 @@ class CourseModuleViewModel extends ReactiveViewModel {
Future<void> navigateToCourseLessonDetail(CourseLesson lesson) async =>
await _navigationService.navigateToCourseLessonDetailView(lesson: lesson);
Future<void> navigateToCoursePractice(int id) async =>
await _navigationService.navigateToCoursePracticeView(id: id, practice: CoursePractices.lesson,);
// Remote api call
// 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: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/widgets/listenable_assessment_card.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_assessment_app_bar.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../course_practice_viewmodel.dart';
class DuolingoListeningAssessment1Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoListeningPractice1Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoListeningAssessment1Question(
{super.key, required this.assessmentController});
const DuolingoListeningPractice1Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Listening Assessment',
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Listening practice',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceLarge,
_buildLabel(),
verticalSpaceSmall,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
_buildPracticeFormField(viewModel),
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
_buildAssessmentWrapper(viewModel),
if (viewModel.hasPracticeValidationMessage &&
viewModel.focusPractice)
_buildPracticeWrapper(viewModel),
];
Widget _buildTitle() => Text(
@ -85,7 +87,7 @@ class DuolingoListeningAssessment1Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const ListenableAssessmentCard();
Widget _buildQuestion() => const ListenablePracticeCard();
Widget _buildLabel() => Text(
'Your Answer',
@ -93,34 +95,35 @@ class DuolingoListeningAssessment1Question
textAlign: TextAlign.center,
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildPracticeFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
maxLength: 250,
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
Widget _buildPracticeWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasPracticeValidationMessage
? _buildPracticeValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
Widget _buildPracticeValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.practiceValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

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

View File

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

View File

@ -3,31 +3,32 @@ import 'package:stacked/stacked.dart';
import '../../../common/app_colors.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 '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
const DuolingoAssessmentsScreens({super.key});
class DuolingoPracticesScreens
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoPracticesScreens({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(DuolingoViewModel viewModel) => Padding(
Widget _buildBody(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(DuolingoViewModel viewModel) => Column(
Widget _buildColumn(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_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,
);
Widget _buildPracticeColumnWrapper(DuolingoViewModel viewModel) =>
Widget _buildPracticeColumnWrapper(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildPracticeColumnScrollView(viewModel));
Widget _buildPracticeColumnScrollView(DuolingoViewModel viewModel) =>
Widget _buildPracticeColumnScrollView(CoursePracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildPracticeColumn(viewModel),
);
Widget _buildPracticeColumn(DuolingoViewModel viewModel) => Column(
Widget _buildPracticeColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPracticeColumnChildren(viewModel),
);
List<Widget> _buildPracticeColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildPracticeColumnChildren(
CoursePracticeViewModel viewModel) =>
[
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
@ -72,14 +75,15 @@ class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
style: style14DG400,
);
Widget _buildListView(DuolingoViewModel viewModel) => GridView.builder(
Widget _buildListView(CoursePracticeViewModel viewModel) => GridView.builder(
shrinkWrap: true,
itemCount: viewModel.assessments.length,
itemCount: viewModel.practices.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildCard(
title: viewModel.assessments[index]['label'],
onTap: () => viewModel.setSelectedAssessment(
page: 1, assessment: viewModel.assessments[index])),
title: viewModel.practices[index].title ?? '', onTap: () {}
// onTap: () => viewModel.setSelectedAssessment(
// page: 1, assessment: viewModel.assessments[index])
),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
@ -92,7 +96,7 @@ class DuolingoAssessmentsScreens extends ViewModelWidget<DuolingoViewModel> {
required String title,
required GestureTapCallback onTap,
}) =>
DuolingoAssessmentCard(
DuolingoPracticeCard(
title: title,
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: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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment2Answer
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment2Answer({super.key});
class DuolingoSpeakingPractice2Answer
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice2Answer({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -52,12 +52,14 @@ class DuolingoSpeakingAssessment2Answer
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0),
);
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
@ -89,7 +91,7 @@ class DuolingoSpeakingAssessment2Answer
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?',
subtitle:
'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,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -1,17 +1,17 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment2Question
class DuolingoSpeakingPractice2Question
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment2Question({
const DuolingoSpeakingPractice2Question({
super.key,
});
@ -52,8 +52,8 @@ class DuolingoSpeakingAssessment2Question
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0),
);
@ -85,7 +85,7 @@ class DuolingoSpeakingAssessment2Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?',
subtitle:
'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/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';
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 DuolingoSpeakingAssessment2Review
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment2Review({super.key});
class DuolingoSpeakingPractice2Review
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice2Review({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildImageSectionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel)
_buildPracticeReviewSection(viewModel)
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -50,17 +50,19 @@ class DuolingoSpeakingAssessment2Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildImageSectionIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildImageSectionIndenter(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildImageSectionWrapper(viewModel),
);
Widget _buildImageSectionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildImageSectionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildImageSectionChildren(),
@ -88,12 +90,12 @@ class DuolingoSpeakingAssessment2Review
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'How has growing up in your hometown influenced you?',
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));
Widget _buildPracticeReviewSection(CoursePracticeViewModel viewModel) =>
SpeakingPracticeReviewSection(onTap: () => viewModel.goTo(5));
}

View File

@ -1,19 +1,19 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart';
import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoSpeakingAssessment3Answer
class DuolingoSpeakingPractice3Answer
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment3Answer({super.key});
const DuolingoSpeakingPractice3Answer({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
@ -52,8 +52,8 @@ class DuolingoSpeakingAssessment3Answer
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0),
);
@ -74,7 +74,7 @@ class DuolingoSpeakingAssessment3Answer
Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle:
'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: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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment3Question
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment3Question({
class DuolingoSpeakingPractice3Question
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice3Question({
super.key,
});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -52,12 +52,14 @@ class DuolingoSpeakingAssessment3Question
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0),
);
Widget _buildSpeakingIndicatorWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildSpeakingIndicatorWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
@ -85,17 +87,18 @@ class DuolingoSpeakingAssessment3Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
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 _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/countdown_timer.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/speaking_indicator.dart';
import '../../../widgets/speaking_label.dart';
import '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment4Answer
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment4Answer({super.key});
class DuolingoSpeakingPractice4Answer
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice4Answer({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildAnswerSectionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -52,15 +52,17 @@ class DuolingoSpeakingAssessment4Answer
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
title: 'Speaking Assessment',
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Speaking practice',
onClose: () => viewModel.goTo(0),
);
Widget _buildCountdownWrapper() =>
const Align(alignment: Alignment.centerRight, child: CountdownTimer());
Widget _buildAnswerSectionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAnswerSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildAnswerSectionChildren(),
@ -96,12 +98,13 @@ class DuolingoSpeakingAssessment4Answer
color: kcWhite,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

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

View File

@ -3,44 +3,44 @@ 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_practice_app_bar.dart';
import '../../../widgets/speaking_practice_review_section.dart';
import '../../../widgets/dwarf_tile.dart';
import '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoSpeakingAssessment4Review
extends ViewModelWidget<DuolingoViewModel> {
const DuolingoSpeakingAssessment4Review({super.key});
class DuolingoSpeakingPractice4Review
extends ViewModelWidget<CoursePracticeViewModel> {
const DuolingoSpeakingPractice4Review({super.key});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildQuestionIndenter(viewModel),
_buildAssessmentReviewSection(viewModel)
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -48,12 +48,13 @@ class DuolingoSpeakingAssessment4Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildQuestionIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildDwarfWrapper(),
);
@ -69,6 +70,6 @@ class DuolingoSpeakingAssessment4Review
style: style16DG600,
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
SpeakingAssessmentReviewSection(onTap: () => viewModel.goTo(5));
Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../duolingo_viewmodel.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment1Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
const DuolingoWritingAssessment1Question(
{super.key, required this.assessmentController});
class DuolingoWritingPractice1Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingPractice1Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceSmall,
_buildImageContainer(),
verticalSpaceMedium,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
_buildAssessmentWrapper(viewModel),
];
@ -97,34 +98,35 @@ class DuolingoWritingAssessment1Question
width: double.maxFinite,
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
maxLength: 250,
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -1,18 +1,18 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../duolingo/duolingo_viewmodel.dart';
class DuolingoWritingAssessment1Review
class DuolingoWritingPractice1Review
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
final TextEditingController practiceController;
const DuolingoWritingAssessment1Review(
{super.key, required this.assessmentController});
const DuolingoWritingPractice1Review(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
@ -49,7 +49,7 @@ class DuolingoWritingAssessment1Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
@ -112,13 +112,13 @@ class DuolingoWritingAssessment1Review
maxLines: 5,
enabled: false,
maxLength: 250,
controller: assessmentController,
controller: practiceController,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
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/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/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Answer
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice2Answer
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment2Answer(
{super.key, required this.assessmentController});
const DuolingoWritingPractice2Answer(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceMedium,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
_buildAssessmentWrapper(viewModel),
];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment2Answer
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle:
'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(
maxLines: 5,
maxLength: 250,
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -3,65 +3,66 @@ import 'package:stacked/stacked.dart';
import '../../../common/app_colors.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/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice2Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment2Question(
{super.key, required this.assessmentController});
const DuolingoWritingPractice2Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
_buildSubtitle(),
verticalSpaceMedium,
@ -81,16 +82,17 @@ class DuolingoWritingAssessment2Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle:
'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),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -1,48 +1,48 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment2Review
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice2Review
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment2Review(
{super.key, required this.assessmentController});
const DuolingoWritingPractice2Review(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel),
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment2Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildExpandedBody(DuolingoViewModel viewModel) =>
Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) =>
Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel),
);
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel),
);
List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) =>
CoursePracticeViewModel viewModel) =>
[
verticalSpaceLarge,
_buildTitle(),
@ -93,28 +95,28 @@ class DuolingoWritingAssessment2Review
child: _buildQuestion(),
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
subtitle:
'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: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel),
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
enabled: false,
maxLength: 250,
controller: assessmentController,
controller: practiceController,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5));
Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
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/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/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment3Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice3Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment3Question(
{super.key, required this.assessmentController});
const DuolingoWritingPractice3Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceMedium,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
_buildAssessmentWrapper(viewModel),
];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment3Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: '1. Can you tell me about your favorite type of music?',
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
maxLength: 250,
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -1,48 +1,48 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment3Review
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice3Review
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment3Review(
{super.key, required this.assessmentController});
const DuolingoWritingPractice3Review(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel),
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment3Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildExpandedBody(DuolingoViewModel viewModel) =>
Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) =>
Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel),
);
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel),
);
List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) =>
CoursePracticeViewModel viewModel) =>
[
verticalSpaceLarge,
_buildTitle(),
@ -93,27 +95,27 @@ class DuolingoWritingAssessment3Review
child: _buildQuestion(),
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'Can you tell me about your favorite type of music?');
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel),
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
enabled: false,
maxLength: 250,
controller: assessmentController,
controller: practiceController,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5));
Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
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/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/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment4Question
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice4Question
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment4Question(
{super.key, required this.assessmentController});
const DuolingoWritingPractice4Question(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildBodyColumnWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
);
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildQuestionWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Writing Assessment',
onClose: () => viewModel.goTo(0),
);
Widget _buildQuestionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionWrapper(CoursePracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionChildren(viewModel),
);
List<Widget> _buildQuestionChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildQuestionChildren(CoursePracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildQuestion(),
verticalSpaceMedium,
_buildAssessmentFormField(viewModel),
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
verticalSpaceTiny,
if (viewModel.hasAssessmentValidationMessage &&
viewModel.focusAssessment)
viewModel.focusPractice)
_buildAssessmentWrapper(viewModel),
];
@ -82,38 +83,39 @@ class DuolingoWritingAssessment4Question
textAlign: TextAlign.center,
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: '1. Can you tell me about your favorite type of music?',
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
maxLength: 250,
controller: assessmentController,
onTap: viewModel.setAssessmentFocus,
controller: practiceController,
onTap: viewModel.setPracticeFocus,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentWrapper(CoursePracticeViewModel viewModel) =>
viewModel.hasAssessmentValidationMessage
? _buildAssessmentValidator(viewModel)
: Container();
Widget _buildAssessmentValidator(DuolingoViewModel viewModel) => Text(
Widget _buildAssessmentValidator(CoursePracticeViewModel viewModel) => Text(
viewModel.assessmentValidationMessage!,
style: style12R700,
);
Widget _buildContinueButtonWrapper(DuolingoViewModel viewModel) => Padding(
Widget _buildContinueButtonWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(DuolingoViewModel viewModel) =>
Widget _buildContinueButton(CoursePracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Submit',

View File

@ -1,48 +1,48 @@
import 'package:flutter/material.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/ui_helpers.dart';
import '../../../widgets/duolingo_assessment_app_bar.dart';
import '../../../widgets/duolingo_assessment_question_card.dart';
import '../duolingo_viewmodel.dart';
import '../../../widgets/duolingo_practice_app_bar.dart';
import '../../../widgets/duolingo_practice_question_card.dart';
import '../course_practice_viewmodel.dart';
class DuolingoWritingAssessment4Review
extends ViewModelWidget<DuolingoViewModel> {
final TextEditingController assessmentController;
class DuolingoWritingPractice4Review
extends ViewModelWidget<CoursePracticeViewModel> {
final TextEditingController practiceController;
const DuolingoWritingAssessment4Review(
{super.key, required this.assessmentController});
const DuolingoWritingPractice4Review(
{super.key, required this.practiceController});
@override
Widget build(BuildContext context, DuolingoViewModel viewModel) =>
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(DuolingoViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper(CoursePracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(DuolingoViewModel viewModel) =>
Widget _buildScaffold(CoursePracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumn(viewModel));
Widget _buildBodyColumn(DuolingoViewModel viewModel) => Column(
Widget _buildBodyColumn(CoursePracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(DuolingoViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren(CoursePracticeViewModel viewModel) => [
_buildAppBarIndenter(viewModel),
_buildExpandedBody(viewModel),
];
Widget _buildAppBarIndenter(DuolingoViewModel viewModel) => Padding(
Widget _buildAppBarIndenter(CoursePracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppBarWrapper(viewModel),
);
Widget _buildAppBarWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildAppBarWrapper(CoursePracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
@ -50,27 +50,29 @@ class DuolingoWritingAssessment4Review
],
);
Widget _buildAppBar(DuolingoViewModel viewModel) => DuolingoAssessmentAppBar(
Widget _buildAppBar(CoursePracticeViewModel viewModel) =>
DuolingoPracticeAppBar(
title: 'Feedback',
onClose: () => viewModel.goTo(0),
);
Widget _buildExpandedBody(DuolingoViewModel viewModel) =>
Widget _buildExpandedBody(CoursePracticeViewModel viewModel) =>
Expanded(child: _buildBodyScroller(viewModel));
Widget _buildBodyScroller(DuolingoViewModel viewModel) =>
Widget _buildBodyScroller(CoursePracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildQuestionSectionWrapper(viewModel),
);
Widget _buildQuestionSectionWrapper(DuolingoViewModel viewModel) => Column(
Widget _buildQuestionSectionWrapper(CoursePracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildQuestionQuestionSectionChildren(viewModel),
);
List<Widget> _buildQuestionQuestionSectionChildren(
DuolingoViewModel viewModel) =>
CoursePracticeViewModel viewModel) =>
[
verticalSpaceLarge,
_buildTitle(),
@ -93,27 +95,27 @@ class DuolingoWritingAssessment4Review
child: _buildQuestion(),
);
Widget _buildQuestion() => const DuolingoAssessmentQuestionCard(
Widget _buildQuestion() => const DuolingoPracticeQuestionCard(
title: 'Can you tell me about your favorite type of music?');
Widget _buildAssessmentFormFieldWrapper(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormFieldWrapper(CoursePracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAssessmentFormField(viewModel),
);
Widget _buildAssessmentFormField(DuolingoViewModel viewModel) =>
Widget _buildAssessmentFormField(CoursePracticeViewModel viewModel) =>
TextFormField(
maxLines: 5,
enabled: false,
maxLength: 250,
controller: assessmentController,
controller: practiceController,
decoration: inputDecoration(
focus: true,
hint: 'Start writing here...',
filled: assessmentController.text.isNotEmpty),
filled: practiceController.text.isNotEmpty),
);
Widget _buildAssessmentReviewSection(DuolingoViewModel viewModel) =>
DuolingoAssessmentReviewSection(onTap: () => viewModel.goTo(5));
Widget _buildAssessmentReviewSection(CoursePracticeViewModel viewModel) =>
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_annotations.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/validators/form_validator.dart';
@ -44,130 +14,7 @@ import 'duolingo_viewmodel.dart';
class DuolingoView extends StackedView<DuolingoViewModel> with $DuolingoView {
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
DuolingoViewModel viewModelBuilder(BuildContext context) =>
@ -198,40 +45,40 @@ class DuolingoView extends StackedView<DuolingoViewModel> with $DuolingoView {
index: viewModel.currentPage, children: _buildScreens(viewModel));
List<Widget> _buildScreens(DuolingoViewModel viewModel) => [
_buildDuolingoAssessmentsScreen(),
_buildDuolingoIntroScreen(viewModel),
_buildDuolingoQuestionScreen(viewModel),
_buildDuolingoAnswerScreen(viewModel),
_buildDuolingoReviewScreen(viewModel),
_buildDuolingoRetakeScreen(viewModel),
_buildDuolingoFinishScreen(viewModel),
// _buildDuolingoAssessmentsScreen(),
// _buildDuolingoIntroScreen(viewModel),
// _buildDuolingoQuestionScreen(viewModel),
// _buildDuolingoAnswerScreen(viewModel),
// _buildDuolingoReviewScreen(viewModel),
// _buildDuolingoRetakeScreen(viewModel),
// _buildDuolingoFinishScreen(viewModel),
];
Widget _buildDuolingoAssessmentsScreen() =>
const DuolingoAssessmentsScreens();
Widget _buildDuolingoIntroScreen(DuolingoViewModel viewModel) =>
DuolingoIntroScreen(
type: viewModel.selectedAssessment['type'],
title: viewModel.selectedAssessment['intro_title'],
subtitle: viewModel.selectedAssessment['intro_subtitle']);
Widget _buildDuolingoQuestionScreen(DuolingoViewModel viewModel) =>
_buildQuestionScreen(viewModel);
Widget _buildDuolingoAnswerScreen(DuolingoViewModel viewModel) =>
_buildAnswerScreen(viewModel);
Widget _buildDuolingoReviewScreen(DuolingoViewModel viewModel) =>
_buildReviewScreen(viewModel);
Widget _buildDuolingoRetakeScreen(DuolingoViewModel viewModel) =>
DuolingoRetakeScreen(
title: viewModel.selectedAssessment['outro_title'],
subtitle: viewModel.selectedAssessment['outro_subtitle']);
Widget _buildDuolingoFinishScreen(DuolingoViewModel viewModel) =>
DuolingoFinishScreen(
title: viewModel.selectedAssessment['outro_title'],
subtitle: viewModel.selectedAssessment['outro_subtitle']);
// Widget _buildDuolingoAssessmentsScreen() =>
// const DuolingoAssessmentsScreens();
//
// Widget _buildDuolingoIntroScreen(DuolingoViewModel viewModel) =>
// DuolingoIntroScreen(
// type: viewModel.selectedAssessment['type'],
// title: viewModel.selectedAssessment['intro_title'],
// subtitle: viewModel.selectedAssessment['intro_subtitle']);
//
// Widget _buildDuolingoQuestionScreen(DuolingoViewModel viewModel) =>
// _buildQuestionScreen(viewModel);
//
// Widget _buildDuolingoAnswerScreen(DuolingoViewModel viewModel) =>
// _buildAnswerScreen(viewModel);
//
// Widget _buildDuolingoReviewScreen(DuolingoViewModel viewModel) =>
// _buildReviewScreen(viewModel);
//
// Widget _buildDuolingoRetakeScreen(DuolingoViewModel viewModel) =>
// DuolingoRetakeScreen(
// title: viewModel.selectedAssessment['outro_title'],
// subtitle: viewModel.selectedAssessment['outro_subtitle']);
//
// Widget _buildDuolingoFinishScreen(DuolingoViewModel viewModel) =>
// DuolingoFinishScreen(
// title: viewModel.selectedAssessment['outro_title'],
// 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_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.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/second_failure_screen.dart';
import 'package:yimaru_app/ui/views/failure/screens/third_failure_screen.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import 'failure_viewmodel.dart';
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_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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/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';
class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
final String label;
final GestureTapCallback onTap;
const FirstFailureScreen({super.key,required this.onTap,required this.label});
const FirstFailureScreen(
{super.key, required this.onTap, required this.label});
@override
Widget build(BuildContext context, FailureViewModel viewModel) =>
Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
@ -59,17 +56,17 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
@ -110,56 +107,51 @@ class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
horizontalSpaceSmall,
_buildRetryButtonWrapper()
];
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
horizontalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingText() =>
Text('$label...', style: style16W600);
Widget _buildLoadingText() => Text('$label...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap,
child: _buildRetryButton(),
);
onTap: onTap,
child: _buildRetryButton(),
);
Widget _buildRetryButton() => Text(
'Retry',
style: style16W600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
'Retry',
style: style16W600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
}

View File

@ -1,13 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
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_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
@ -21,145 +18,140 @@ class ThirdFailureScreen extends ViewModelWidget<FailureViewModel> {
Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
backgroundColor:kcWhite,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcWhite,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_purple.svg',
height: 25,
);
'assets/icons/logo_purple.svg',
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' ቦታ ',
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' ቦታ ',
style: style25P600,
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
);
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_3.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
'assets/images/landing_3.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
horizontalSpaceSmall,_buildRetryButtonWrapper()
];
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
horizontalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingText() =>
Text('$label...', style: style16P600);
Widget _buildLoadingText() => Text('$label...', style: style16P600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcPrimaryColor);
Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap,
child: _buildRetryButton(),
);
onTap: onTap,
child: _buildRetryButton(),
);
Widget _buildRetryButton() => Text(
'Retry',
style: style16P600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
'Retry',
style: style16P600.copyWith(
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/translations/locale_keys.g.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/profile/profile_view.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../widgets/coming_soon.dart';
import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> {

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stacked/stacked.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_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_completion_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 '../../common/app_colors.dart';
import '../../widgets/cancel_learn_practice_sheet.dart';
import '../../widgets/cancel_practice_sheet.dart';
import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
@ -80,8 +80,7 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
},
child: _buildScaffoldWrapper(viewModel));
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
@ -101,7 +100,7 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
: _buildBody(viewModel);
Widget _buildPageLoadingIndicator(LearnPracticeViewModel viewModel) =>
LearnLoadingScreen(
PracticeLoadingScreen(
isLoading: viewModel.busy(StateObjects.learnPractice),
onTap: () async =>
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:waveform_recorder/waveform_recorder.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_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/wave_wrapper.dart';
@ -365,8 +365,7 @@ class InteractLearnPracticeScreen
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
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/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart';
@ -104,8 +104,7 @@ class LearnPracticeAppreciationScreen
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
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/translations/locale_keys.g.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/small_app_bar.dart';
@ -87,8 +87,7 @@ class LearnPracticeDescriptionScreen
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
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/enmus.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/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart';
@ -106,8 +106,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
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/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/cancel_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/page_loading_indicator.dart';
import '../../../widgets/small_app_bar.dart';
@ -120,8 +120,7 @@ class LearnPracticeResultScreen
await _showSheet(context: context, viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
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 '../../../common/app_colors.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';
class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
@ -111,8 +111,7 @@ class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
title:
'${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})');
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',

View File

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

View File

@ -60,8 +60,8 @@ class NotificationView extends StackedView<NotificationViewModel> {
List<Widget> _buildColumnChildren(NotificationViewModel viewModel) => [
verticalSpaceMedium,
_buildAppBarWrapper(viewModel),
verticalSpaceMedium,
_buildNotificationsColumnWrapper(viewModel)
verticalSpaceMedium,
_buildNotificationsColumnWrapper(viewModel)
];
Widget _buildAppBarWrapper(NotificationViewModel viewModel) => Padding(
@ -75,7 +75,6 @@ class NotificationView extends StackedView<NotificationViewModel> {
title: LocaleKeys.notifications.tr(),
);
Widget _buildNotificationsColumnWrapper(NotificationViewModel viewModel) =>
Expanded(child: _buildNotificationsColumnScrollView(viewModel));
@ -87,26 +86,24 @@ class NotificationView extends StackedView<NotificationViewModel> {
Widget _buildListViewBuilder(NotificationViewModel viewModel) =>
viewModel.busy(StateObjects.notifications)
? _buildProgressIndicator()
: _buildListView(viewModel);
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(NotificationViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.notifications.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildCard(
notification: viewModel.notifications[index],
),
);
shrinkWrap: true,
itemCount: viewModel.notifications.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildCard(
notification: viewModel.notifications[index],
),
);
Widget _buildCard({
required InAppNotification notification,
}) =>
NotificationCard(notification: notification);
}

View File

@ -10,7 +10,6 @@ import '../../../app/app.locator.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart';
@ -32,7 +31,7 @@ class ProfileViewModel extends ReactiveViewModel {
@override
List<ListenableServiceMixin> get listenableServices =>
[_authenticationService,_inAppNotificationService];
[_authenticationService, _inAppNotificationService];
// Current 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/ui_helpers.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';
@ -13,29 +12,29 @@ import '../../../widgets/custom_circular_progress_indicator.dart';
class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label;
const FirstStartupScreen({super.key,this.label});
const FirstStartupScreen({super.key, this.label});
@override
Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
@ -57,17 +56,17 @@ class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
@ -108,44 +107,39 @@ class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
}

View File

@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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 '../../../widgets/custom_circular_progress_indicator.dart';
@ -13,141 +12,134 @@ import '../startup_viewmodel.dart';
class SecondStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label;
const SecondStartupScreen({super.key,this.label});
const SecondStartupScreen({super.key, this.label});
@override
Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
backgroundColor: Colors.amber,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: Colors.amber,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_purple.svg',
height: 25,
);
'assets/icons/logo_purple.svg',
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' እድሜ ',
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' እድሜ ',
style: style25P600,
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
);
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_2.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
'assets/images/landing_2.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16P600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcPrimaryColor);
}

View File

@ -4,7 +4,6 @@ import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.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 '../../../widgets/custom_circular_progress_indicator.dart';
@ -13,141 +12,134 @@ import '../startup_viewmodel.dart';
class ThirdStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label;
const ThirdStartupScreen({super.key,this.label});
const ThirdStartupScreen({super.key, this.label});
@override
Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
backgroundColor:kcWhite,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcWhite,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_purple.svg',
height: 25,
);
'assets/icons/logo_purple.svg',
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' ቦታ ',
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' ቦታ ',
style: style25P600,
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
);
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_3.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
'assets/images/landing_3.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16P600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
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/scheduler.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.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/second_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/enmus.dart';
import '../../common/translations/locale_keys.g.dart';
import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> {

View File

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

View File

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

View File

@ -1,40 +1,76 @@
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/views/course_practice/course_practice_viewmodel.dart';
import '../common/helper_functions.dart';
import '../common/ui_helpers.dart';
class CountdownTimer extends StatelessWidget {
const CountdownTimer({super.key});
class CountdownTimer extends ViewModelWidget<CoursePracticeViewModel> {
final String? time;
const CountdownTimer({super.key, this.time});
Future<void> _stopRecording(CoursePracticeViewModel viewModel)async{
await viewModel.stopRecording();
viewModel.setNextButton();
}
@override
Widget build(BuildContext context) => _buildContainer();
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container(
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: kcPrimaryColor.withValues(alpha: 0.1),
Widget _buildContainer(CoursePracticeViewModel viewModel) => Container(
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: kcPrimaryColor.withValues(alpha: 0.1),
),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: _buildRow(viewModel),
);
Widget _buildRow(CoursePracticeViewModel viewModel) => Row(
children: [
_buildClockIcon(),
horizontalSpaceTiny,
_buildCountdownState(viewModel),
],
);
Widget _buildCountdownState(CoursePracticeViewModel viewModel) => time == null ? _buildCountdownTime(viewModel):_buildCountdownText();
Widget _buildCountdownText() => Text(
time ?? '',
style: style16P600,
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'],
),
),
),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: _buildRow(),
);
Widget _buildRow() => Row(
children: [
_buildClockIcon(),
horizontalSpaceTiny,
_buildCountdownTime(),
],
);
Widget _buildCountdownTime() => Text(
'0:20',
style: style16P600,
textAlign: TextAlign.center,
);
Widget _buildClockIcon() => const Icon(
Icons.timer_outlined,
color: kcPrimaryColor,
);
Icons.timer_outlined,
color: kcPrimaryColor,
);
}

View File

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

View File

@ -1,68 +1,89 @@
import 'package:audioplayers/audioplayers.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/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 subtitle;
const CustomResponseCard(
{super.key, required this.title, required this.subtitle});
{super.key,
required this.voice,
required this.title,
});
@override
Widget build(BuildContext context) => _buildContainer();
Widget build(BuildContext context, CoursePracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container(
decoration: BoxDecoration(
color: kcWhite,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
child: _buildRow(),
);
Widget _buildContainer(CoursePracticeViewModel viewModel) => Container(
decoration: BoxDecoration(
color: kcWhite,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
child: _buildRow(viewModel),
);
Widget _buildRow() => Row(
children: [_buildPlayButton(), _buildColumnWrapper()],
);
Widget _buildRow(CoursePracticeViewModel viewModel) => Row(
children: [_buildPlayButton(viewModel), _buildColumnWrapper()],
);
Widget _buildPlayButton() => ElevatedButton(
onPressed: () {},
style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(5)),
shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
),
child: _buildPlayIcon(),
);
Widget _buildPlayButton(CoursePracticeViewModel viewModel) => ElevatedButton(
style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(5)),
shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
),
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(
Icons.play_arrow_rounded,
size: 25,
color: kcWhite,
);
Icons.play_arrow_rounded,
size: 25,
color: kcWhite,
);
Widget _buildColumnWrapper() => Expanded(child: _buildColumn());
Widget _buildPauseIcon() => const Icon(
Icons.pause,
size: 25,
color: kcWhite,
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [_buildTitle(), _buildSubtitle()];
Widget _buildColumnWrapper() => Expanded(child: _buildTitle());
Widget _buildTitle() => Text(
title,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
Widget _buildSubtitle() => Text(
subtitle,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
title,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
}

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
class DuolingoAssessmentAppBar extends StatelessWidget {
class DuolingoPracticeAppBar extends StatelessWidget {
final String? title;
final GestureTapCallback? onClose;
const DuolingoAssessmentAppBar({super.key, this.onClose, this.title});
const DuolingoPracticeAppBar({super.key, this.onClose, this.title});
@override
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 'custom_elevated_button.dart';
class DuolingoAssessmentCard extends StatelessWidget {
class DuolingoPracticeCard extends StatelessWidget {
final String title;
final GestureTapCallback? onTap;
const DuolingoAssessmentCard({super.key, this.onTap, required this.title});
const DuolingoPracticeCard({super.key, this.onTap, required this.title});
@override
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/ui_helpers.dart';
class DuolingoAssessmentQuestionCard extends StatelessWidget {
class DuolingoPracticeQuestionCard extends StatelessWidget {
final String? title;
final String? subtitle;
const DuolingoAssessmentQuestionCard({super.key, this.title, this.subtitle});
const DuolingoPracticeQuestionCard({super.key, this.title, this.subtitle});
@override
Widget build(BuildContext context) => _buildContainer();

View File

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

View File

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

View File

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

View File

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

View File

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

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/small_app_bar.dart';
import '../../../common/app_colors.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../common/ui_helpers.dart';
import '../common/app_colors.dart';
import '../common/translations/locale_keys.g.dart';
import '../common/ui_helpers.dart';
class LearnLoadingScreen extends StatelessWidget {
class PracticeLoadingScreen extends StatelessWidget {
final bool isEmpty;
final bool isLoading;
final GestureTapCallback? onPop;
final GestureTapCallback? onTap;
const LearnLoadingScreen(
const PracticeLoadingScreen(
{super.key,
this.onTap,
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');
}

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