- feat: Add localization according to the UAT comments
Merge branch 'release/0.1.16'
This commit is contained in:
commit
3cc5753308
85
assets/translations/am.json
Normal file
85
assets/translations/am.json
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
|
||||
"welcome_back": "እንኳን በደህና ተመለሱ",
|
||||
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ",
|
||||
"email": "ኢሜይል",
|
||||
"password": "የይለፍ ቃል",
|
||||
"forgot_password": "የይለፍ ቃል ረሱ?",
|
||||
"cont": "ቀጥል",
|
||||
"register": "ይመዝገቡ",
|
||||
"login_with_google": "በጉግል ይግቡ",
|
||||
"or": "ወይም",
|
||||
"login_with_phone": "በስልክ ቁጥር ይግቡ",
|
||||
"create_account": "አዲስ መለያ ይፍጠሩ",
|
||||
"already_have_account": "መለያ አለዎት?",
|
||||
"login": " ይግቡ ",
|
||||
"register_with_google": "በጉግል ይመዝገቡ",
|
||||
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
|
||||
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
|
||||
"login_with_email": "በኢሜይል ይግቡ",
|
||||
"create_password": "የይለፍ ቃል ይፍጠሩ",
|
||||
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
|
||||
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።" ,
|
||||
"reset_password": " የይለፍ ቃልን ይቀይሩ ",
|
||||
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።" ,
|
||||
"please_wait": "እባክዎ ይጠብቁ",
|
||||
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል" ,
|
||||
"reset_code": " የመቀየሪያ ኮድ ",
|
||||
"new_password": "አዲስ የይለፍ ቃል",
|
||||
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
|
||||
"view_course": " ኮርሱን ይመልከቱ ",
|
||||
"take_practice": " ልምምድ ያድርጉ ",
|
||||
"your_current_level": "የአሁኑ ደረጃዎ",
|
||||
"overall_progress": "አጠቃላይ እድገት",
|
||||
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ",
|
||||
"view_module": "ሞጁሉን ይመልከቱ",
|
||||
"progress": "እድገት",
|
||||
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
|
||||
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
|
||||
"practice": "ልምምድ",
|
||||
"start": "ጀምር",
|
||||
"in_progress": "በሂደት ላይ",
|
||||
"hello": "ሰላም",
|
||||
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
|
||||
"learn": "ይማሩ ",
|
||||
"course": "ኮርስ",
|
||||
"profile": " ፕሮፋይል ",
|
||||
"speaking_partner": "የንግግር ጓደኛ ",
|
||||
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
|
||||
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
|
||||
"start_practice": "ልምምድ ጀምር",
|
||||
"almost_there": "ሊጨርሱ ተቃርበዋል",
|
||||
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
|
||||
"continue_practice": "ልምምዱን ይቀጥሉ",
|
||||
"end_session": "ክፍለ ጊዜውን ያብቁ ",
|
||||
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
|
||||
"practice_speaking": "ንግግርን ይለማመዱ ",
|
||||
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
|
||||
"reply": "እንደገና አዳምጥ",
|
||||
"cancel": "ይቅር",
|
||||
"you_are_speaking": "እየተናገሩ ነው",
|
||||
"practice_completed": "ልምምዱ ተጠናቅቋል",
|
||||
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
|
||||
"practice_again": "እንደገና ይለማመዱ",
|
||||
"conversation_review": "የንግግር ግምገማ ",
|
||||
"result": "ውጤት",
|
||||
"quick_tip": "ጠቃሚ ምክር",
|
||||
"retry": "እንደገና ይሞክሩ",
|
||||
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
|
||||
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
|
||||
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
|
||||
"hi": "ሰላም",
|
||||
"edit_profile": "መገለጫ ያስተካክሉ",
|
||||
"first_name": "የመጀመሪያ ስም",
|
||||
"last_name": "የአባት ስም",
|
||||
"gender": "ፆታ",
|
||||
"male": "ወንድ",
|
||||
"female": "ሴት",
|
||||
"phone_number": "የስልክ ቁጥር",
|
||||
"country": "ሀገር",
|
||||
"region": "ክልል",
|
||||
"occupation": "የስራ መስክ ",
|
||||
"save_changes": "ለውጦችን ያስቀምጡ"
|
||||
|
||||
}
|
||||
|
||||
83
assets/translations/en.json
Normal file
83
assets/translations/en.json
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
{
|
||||
"welcome_back": "Welcome back",
|
||||
"dont_have_account": "Don't have an account? Register",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"forgot_password": "Forgot password?",
|
||||
"cont": "Continue",
|
||||
"register": "Register",
|
||||
"login_with_google": "Login with Google",
|
||||
"or": "Or",
|
||||
"login_with_phone": "Login with phone number",
|
||||
"create_account": "Create an account",
|
||||
"already_have_account": "Already have an account?",
|
||||
"login": "Login",
|
||||
"register_with_google": "Register with Google",
|
||||
"register_with_phone": "Register with phone number",
|
||||
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
|
||||
"login_with_email": "Login with email",
|
||||
"create_password": "Create password",
|
||||
"confirm_password": "Confirm password",
|
||||
"sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’",
|
||||
"reset_password": "Reset Password",
|
||||
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
|
||||
"please_wait": "Please wait",
|
||||
"reset_code_sent": "Reset code sent successfully",
|
||||
"reset_code": "Reset code",
|
||||
"new_password": "New password",
|
||||
"logged_in_successfully": "Logged in successfully",
|
||||
"view_course": "View course",
|
||||
"take_practice": "Take practice",
|
||||
"your_current_level": "Your current level",
|
||||
"overall_progress": "Overall progress",
|
||||
"great_work": "Keep up the great work! You're doing amazing",
|
||||
"view_module": "View module",
|
||||
"progress": "Progress",
|
||||
"keep_going": "Let's keep going - you're more than half there",
|
||||
"lessons_in_module": "Lessons in this module",
|
||||
"practice": "Practice",
|
||||
"start": "Start",
|
||||
"in_progress": "In Progress",
|
||||
"hello": "Hello",
|
||||
"ready_to_learn": "Ready to keep learning English today",
|
||||
"learn": "Learn",
|
||||
"course": "Course",
|
||||
"profile": "Profile",
|
||||
"speaking_partner": "Speaking partner",
|
||||
"practice_what_you_learned": "Let's practice what you just learnt",
|
||||
"practice_questions": "I will ask you a few questions and you can respond",
|
||||
"start_practice": "Start practice",
|
||||
"almost_there": "You're almost there",
|
||||
"finish_session": "Finish the session to see your progress",
|
||||
"continue_practice": "Continue practice",
|
||||
"end_session": "End session",
|
||||
"tap_start_to_listen": "Tap the start button to listen",
|
||||
"practice_speaking": "Practice speaking",
|
||||
"tap_microphone": "Tap the microphone to speak",
|
||||
"reply": "Reply",
|
||||
"cancel": "Cancel",
|
||||
"you_are_speaking": "You're speaking",
|
||||
"practice_completed": "Practice completed",
|
||||
"great_improvement": "You sound more confident this time, great improvement",
|
||||
"practice_again": "Practice again",
|
||||
"conversation_review": "Conversation review",
|
||||
"result": "Result",
|
||||
"quick_tip": "Quick tip",
|
||||
"retry": "Retry",
|
||||
"completed_a1": "Yay, you've completed A1",
|
||||
"analyzing_speaking": "We're now analyzing your speaking skill",
|
||||
"view_profile": "View profile",
|
||||
"hi": "Hi",
|
||||
"edit_profile": "Edit profile",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"gender": "Gender",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"phone_number": "Phone number",
|
||||
"country": "Country",
|
||||
"region": "Region",
|
||||
"occupation": "Occupation",
|
||||
"save_changes": "Save changes"
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart';
|
|||
import 'package:yimaru_app/services/learn_service.dart';
|
||||
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
|
||||
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
|
||||
import 'package:yimaru_app/services/localization_service.dart';
|
||||
// @stacked-import
|
||||
|
||||
@StackedApp(
|
||||
|
|
@ -124,6 +125,7 @@ import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
|
|||
LazySingleton(classType: UrlLauncherService),
|
||||
LazySingleton(classType: PhoneCallerService),
|
||||
LazySingleton(classType: LearnService),
|
||||
LazySingleton(classType: LocalizationService),
|
||||
// @stacked-service
|
||||
],
|
||||
bottomsheets: [
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import '../services/image_downloader_service.dart';
|
|||
import '../services/image_picker_service.dart';
|
||||
import '../services/in_app_update_service.dart';
|
||||
import '../services/learn_service.dart';
|
||||
import '../services/localization_service.dart';
|
||||
import '../services/notification_service.dart';
|
||||
import '../services/permission_handler_service.dart';
|
||||
import '../services/phone_caller_service.dart';
|
||||
|
|
@ -63,4 +64,5 @@ Future<void> setupLocator(
|
|||
locator.registerLazySingleton(() => UrlLauncherService());
|
||||
locator.registerLazySingleton(() => PhoneCallerService());
|
||||
locator.registerLazySingleton(() => LearnService());
|
||||
locator.registerLazySingleton(() => LocalizationService());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@
|
|||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:flutter/material.dart' as _i39;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' as _i39;
|
||||
import 'package:stacked/stacked.dart' as _i1;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i46;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i47;
|
||||
import 'package:yimaru_app/models/course.dart' as _i44;
|
||||
import 'package:yimaru_app/models/course_catalog.dart' as _i46;
|
||||
import 'package:yimaru_app/models/course_lesson.dart' as _i45;
|
||||
import 'package:yimaru_app/models/learn_course.dart' as _i40;
|
||||
import 'package:yimaru_app/models/learn_lesson.dart' as _i42;
|
||||
|
|
@ -664,11 +665,10 @@ class StackedRouter extends _i1.RouterBase {
|
|||
);
|
||||
},
|
||||
_i38.CourseUnitView: (data) {
|
||||
final args = data.getArgs<CourseUnitViewArguments>(
|
||||
orElse: () => const CourseUnitViewArguments(),
|
||||
);
|
||||
final args = data.getArgs<CourseUnitViewArguments>(nullOk: false);
|
||||
return _i39.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => _i38.CourseUnitView(key: args.key),
|
||||
builder: (context) =>
|
||||
_i38.CourseUnitView(key: args.key, catalog: args.catalog),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
|
|
@ -1586,28 +1586,33 @@ class CourseCatalogViewArguments {
|
|||
}
|
||||
|
||||
class CourseUnitViewArguments {
|
||||
const CourseUnitViewArguments({this.key});
|
||||
const CourseUnitViewArguments({
|
||||
this.key,
|
||||
required this.catalog,
|
||||
});
|
||||
|
||||
final _i39.Key? key;
|
||||
|
||||
final _i46.CourseCatalog catalog;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{"key": "$key"}';
|
||||
return '{"key": "$key", "catalog": "$catalog"}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant CourseUnitViewArguments other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other.key == key;
|
||||
return other.key == key && other.catalog == catalog;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return key.hashCode;
|
||||
return key.hashCode ^ catalog.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigatorStateExtension on _i46.NavigationService {
|
||||
extension NavigatorStateExtension on _i47.NavigationService {
|
||||
Future<dynamic> navigateToHomeView({
|
||||
_i39.Key? key,
|
||||
int? routerId,
|
||||
|
|
@ -2216,6 +2221,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
|||
|
||||
Future<dynamic> navigateToCourseUnitView({
|
||||
_i39.Key? key,
|
||||
required _i46.CourseCatalog catalog,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
|
|
@ -2223,7 +2229,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
|||
transition,
|
||||
}) async {
|
||||
return navigateTo<dynamic>(Routes.courseUnitView,
|
||||
arguments: CourseUnitViewArguments(key: key),
|
||||
arguments: CourseUnitViewArguments(key: key, catalog: catalog),
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
|
|
@ -2838,6 +2844,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
|||
|
||||
Future<dynamic> replaceWithCourseUnitView({
|
||||
_i39.Key? key,
|
||||
required _i46.CourseCatalog catalog,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
|
|
@ -2845,7 +2852,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
|
|||
transition,
|
||||
}) async {
|
||||
return replaceWith<dynamic>(Routes.courseUnitView,
|
||||
arguments: CourseUnitViewArguments(key: key),
|
||||
arguments: CourseUnitViewArguments(key: key, catalog: catalog),
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import 'package:yimaru_app/app/app.locator.dart';
|
|||
import 'package:yimaru_app/app/app.router.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/services/notification_service.dart';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:yimaru_app/ui/common/translations/codegen_loader.g.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
|
|
@ -15,26 +16,42 @@ Future<void> main() async {
|
|||
await setupLocator();
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await locator<NotificationService>().initialize();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
setupDialogUi();
|
||||
setupBottomSheetUi();
|
||||
runApp(const MainApp());
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: const [
|
||||
Locale('en'),
|
||||
Locale('am'),
|
||||
],
|
||||
path: 'assets/translations',
|
||||
startLocale: const Locale('en'),
|
||||
assetLoader: const CodegenLoader(),
|
||||
fallbackLocale: const Locale('en'),
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildMaterialWrapper();
|
||||
Widget build(BuildContext context) => _buildMaterialWrapper(context);
|
||||
|
||||
Widget _buildMaterialWrapper() => ToastificationWrapper(
|
||||
child: _buildMaterialApp(),
|
||||
Widget _buildMaterialWrapper(BuildContext context) => ToastificationWrapper(
|
||||
child: _buildMaterialApp(context),
|
||||
);
|
||||
|
||||
Widget _buildMaterialApp() => MaterialApp(
|
||||
Widget _buildMaterialApp(BuildContext context) => MaterialApp(
|
||||
locale: context.locale,
|
||||
initialRoute: Routes.startupView,
|
||||
theme: ThemeData(fontFamily: 'Aeonik'),
|
||||
navigatorKey: StackedService.navigatorKey,
|
||||
supportedLocales: context.supportedLocales,
|
||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||
navigatorObservers: [StackedService.routeObserver],
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
48
lib/models/course_module.dart
Normal file
48
lib/models/course_module.dart
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'course_module.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CourseModule {
|
||||
final int? id;
|
||||
|
||||
final String? name;
|
||||
|
||||
final String? icon;
|
||||
|
||||
final String? thumbnail;
|
||||
|
||||
final String? description;
|
||||
|
||||
@JsonKey(name: 'unit_id')
|
||||
final int? unitId;
|
||||
|
||||
@JsonKey(name: 'sort_order')
|
||||
final int? sortOrder;
|
||||
|
||||
@JsonKey(name: 'has_practice')
|
||||
final bool? hasPractice;
|
||||
|
||||
@JsonKey(name: 'lessons_count')
|
||||
final int? lessonsCount;
|
||||
|
||||
@JsonKey(name: 'practices_count')
|
||||
final int? practice;
|
||||
|
||||
const CourseModule(
|
||||
{this.id,
|
||||
this.icon,
|
||||
this.name,
|
||||
this.unitId,
|
||||
this.practice,
|
||||
this.thumbnail,
|
||||
this.sortOrder,
|
||||
this.hasPractice,
|
||||
this.description,
|
||||
this.lessonsCount});
|
||||
|
||||
factory CourseModule.fromJson(Map<String, dynamic> json) =>
|
||||
_$CourseModuleFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CourseModuleToJson(this);
|
||||
}
|
||||
34
lib/models/course_module.g.dart
Normal file
34
lib/models/course_module.g.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'course_module.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
CourseModule _$CourseModuleFromJson(Map<String, dynamic> json) => CourseModule(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
icon: json['icon'] as String?,
|
||||
name: json['name'] as String?,
|
||||
unitId: (json['unit_id'] as num?)?.toInt(),
|
||||
practice: (json['practices_count'] as num?)?.toInt(),
|
||||
thumbnail: json['thumbnail'] as String?,
|
||||
sortOrder: (json['sort_order'] as num?)?.toInt(),
|
||||
hasPractice: json['has_practice'] as bool?,
|
||||
description: json['description'] as String?,
|
||||
lessonsCount: (json['lessons_count'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CourseModuleToJson(CourseModule instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'icon': instance.icon,
|
||||
'thumbnail': instance.thumbnail,
|
||||
'description': instance.description,
|
||||
'unit_id': instance.unitId,
|
||||
'sort_order': instance.sortOrder,
|
||||
'has_practice': instance.hasPractice,
|
||||
'lessons_count': instance.lessonsCount,
|
||||
'practices_count': instance.practice,
|
||||
};
|
||||
80
lib/models/course_unit.dart
Normal file
80
lib/models/course_unit.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:yimaru_app/models/course_module.dart';
|
||||
|
||||
part 'course_unit.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class CourseUnit {
|
||||
final int? id;
|
||||
|
||||
final String? name;
|
||||
|
||||
final String? thumbnail;
|
||||
|
||||
final String? description;
|
||||
|
||||
final List<CourseModule>? modules;
|
||||
|
||||
@JsonKey(name: 'sort_order')
|
||||
final int? sortOrder;
|
||||
|
||||
@JsonKey(name: 'has_practice')
|
||||
final bool? hasPractice;
|
||||
|
||||
@JsonKey(name: 'lessons_count')
|
||||
final int? lessonsCount;
|
||||
|
||||
@JsonKey(name: 'modules_count')
|
||||
final int? modulesCount;
|
||||
|
||||
@JsonKey(name: 'practices_count')
|
||||
final int? practice;
|
||||
|
||||
@JsonKey(name: 'catalog_course_id')
|
||||
final int? catalogCourseId;
|
||||
|
||||
const CourseUnit(
|
||||
{this.id,
|
||||
this.name,
|
||||
this.modules,
|
||||
this.practice,
|
||||
this.thumbnail,
|
||||
this.sortOrder,
|
||||
this.hasPractice,
|
||||
this.description,
|
||||
this.modulesCount,
|
||||
this.lessonsCount,
|
||||
this.catalogCourseId});
|
||||
|
||||
factory CourseUnit.fromJson(Map<String, dynamic> json) =>
|
||||
_$CourseUnitFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$CourseUnitToJson(this);
|
||||
|
||||
CourseUnit copyWith({
|
||||
int? id,
|
||||
String? name,
|
||||
int? practice,
|
||||
int? sortOrder,
|
||||
bool? hasPractice,
|
||||
int? lessonsCount,
|
||||
int? modulesCount,
|
||||
String? thumbnail,
|
||||
String? description,
|
||||
int? catalogCourseId,
|
||||
List<CourseModule>? modules,
|
||||
}) =>
|
||||
CourseUnit(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
modules: modules ?? this.modules,
|
||||
practice: practice ?? this.practice,
|
||||
thumbnail: thumbnail ?? this.thumbnail,
|
||||
sortOrder: sortOrder ?? this.sortOrder,
|
||||
hasPractice: hasPractice ?? this.hasPractice,
|
||||
description: description ?? this.description,
|
||||
lessonsCount: lessonsCount ?? this.lessonsCount,
|
||||
modulesCount: modulesCount ?? this.modulesCount,
|
||||
catalogCourseId: catalogCourseId ?? this.catalogCourseId,
|
||||
);
|
||||
}
|
||||
38
lib/models/course_unit.g.dart
Normal file
38
lib/models/course_unit.g.dart
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'course_unit.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
CourseUnit _$CourseUnitFromJson(Map<String, dynamic> json) => CourseUnit(
|
||||
id: (json['id'] as num?)?.toInt(),
|
||||
name: json['name'] as String?,
|
||||
modules: (json['modules'] as List<dynamic>?)
|
||||
?.map((e) => CourseModule.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
practice: (json['practices_count'] as num?)?.toInt(),
|
||||
thumbnail: json['thumbnail'] as String?,
|
||||
sortOrder: (json['sort_order'] as num?)?.toInt(),
|
||||
hasPractice: json['has_practice'] as bool?,
|
||||
description: json['description'] as String?,
|
||||
modulesCount: (json['modules_count'] as num?)?.toInt(),
|
||||
lessonsCount: (json['lessons_count'] as num?)?.toInt(),
|
||||
catalogCourseId: (json['catalog_course_id'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CourseUnitToJson(CourseUnit instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'thumbnail': instance.thumbnail,
|
||||
'description': instance.description,
|
||||
'modules': instance.modules,
|
||||
'sort_order': instance.sortOrder,
|
||||
'has_practice': instance.hasPractice,
|
||||
'lessons_count': instance.lessonsCount,
|
||||
'modules_count': instance.modulesCount,
|
||||
'practices_count': instance.practice,
|
||||
'catalog_course_id': instance.catalogCourseId,
|
||||
};
|
||||
|
|
@ -14,6 +14,8 @@ import 'package:yimaru_app/services/dio_service.dart';
|
|||
import 'package:yimaru_app/ui/common/app_constants.dart';
|
||||
|
||||
import '../app/app.locator.dart';
|
||||
import '../models/course_module.dart';
|
||||
import '../models/course_unit.dart';
|
||||
import '../models/learn_course.dart';
|
||||
import '../models/learn_module.dart';
|
||||
import '../models/learn_question.dart';
|
||||
|
|
@ -729,6 +731,54 @@ class ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
// Get course units
|
||||
Future<List<CourseUnit>> getCourseUnits(int id) async {
|
||||
try {
|
||||
List<CourseUnit> units = [];
|
||||
|
||||
final Response response = await _service.dio.get(
|
||||
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kCatalogCoursesUrl/$id/$kUnitsUrl');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = response.data;
|
||||
var decodedData = data['data']['units'] as List;
|
||||
units = decodedData.map(
|
||||
(e) {
|
||||
return CourseUnit.fromJson(e);
|
||||
},
|
||||
).toList();
|
||||
return units;
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get course modules
|
||||
Future<List<CourseModule>> getCourseModules(int id) async {
|
||||
try {
|
||||
List<CourseModule> modules = [];
|
||||
|
||||
final Response response = await _service.dio.get(
|
||||
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kExamPrepUrl/$kUnitsUrl/$id/$kModulesUrl');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var data = response.data;
|
||||
var decodedData = data['data']['modules'] as List;
|
||||
modules = decodedData.map(
|
||||
(e) {
|
||||
return CourseModule.fromJson(e);
|
||||
},
|
||||
).toList();
|
||||
return modules;
|
||||
}
|
||||
return [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/* TO BE MODIFIED*/
|
||||
|
||||
// Get courses
|
||||
|
|
|
|||
|
|
@ -1,13 +1,78 @@
|
|||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/app/app.locator.dart';
|
||||
import 'package:yimaru_app/models/course_progress.dart';
|
||||
import 'package:yimaru_app/services/api_service.dart';
|
||||
|
||||
import '../models/course_catalog.dart';
|
||||
import '../models/course_detail.dart';
|
||||
import '../models/course_module.dart';
|
||||
import '../models/course_unit.dart';
|
||||
|
||||
class CourseService {
|
||||
class CourseService with ListenableServiceMixin {
|
||||
// Dependency injection
|
||||
final _apiService = locator<ApiService>();
|
||||
|
||||
// Initialization
|
||||
courseService() {
|
||||
listenToReactiveValues([_catalogs]);
|
||||
}
|
||||
|
||||
// Course catalogs
|
||||
List<CourseCatalog> _catalogs = [];
|
||||
|
||||
List<CourseCatalog> get catalogs => _catalogs;
|
||||
|
||||
// Course units
|
||||
List<CourseUnit> _units = [];
|
||||
|
||||
List<CourseUnit> get units => _units;
|
||||
|
||||
// Course modules
|
||||
List<CourseModule> _modules = [];
|
||||
|
||||
List<CourseModule> get modules => _modules;
|
||||
|
||||
// Course catalogs
|
||||
Future<void> getCourseCatalogs() async {
|
||||
_catalogs = await _apiService.getCourseCatalogs();
|
||||
_catalogs.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Course units
|
||||
Future<void> getCourseUnits(int id) async {
|
||||
_units = await _apiService.getCourseUnits(id);
|
||||
_units.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Course modules
|
||||
Future<void> getCourseUnitModule({
|
||||
required int id,
|
||||
required int index,
|
||||
}) async {
|
||||
List<CourseModule> modules = await _apiService.getCourseModules(id);
|
||||
|
||||
modules.sort(
|
||||
(a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0),
|
||||
);
|
||||
|
||||
final updatedUnit = _units[index].copyWith(
|
||||
modules: modules,
|
||||
);
|
||||
|
||||
_units[index] = updatedUnit;
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getCourseModules(int id) async {
|
||||
_modules = await _apiService.getCourseModules(id);
|
||||
_modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Get course detail
|
||||
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
||||
final courses = await _apiService.getCourses(id);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ class LearnService with ListenableServiceMixin {
|
|||
final _apiService = locator<ApiService>();
|
||||
|
||||
// Initialization
|
||||
LearnLessonService() {
|
||||
listenToReactiveValues([_programs, _lessons]);
|
||||
learnService() {
|
||||
listenToReactiveValues([_programs, _lessons, _modules]);
|
||||
}
|
||||
|
||||
// Learn program
|
||||
|
|
|
|||
47
lib/services/localization_service.dart
Normal file
47
lib/services/localization_service.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class LocalizationService with ListenableServiceMixin {
|
||||
// Initialization
|
||||
localizationService() {
|
||||
listenToReactiveValues([_selectedLanguage]);
|
||||
}
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> _selectedLanguage = {
|
||||
'code': 'EN',
|
||||
'language': 'English'
|
||||
};
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
final List<Map<String, dynamic>> _languages = [
|
||||
{'code': 'አማ', 'language': 'አማርኛ'},
|
||||
{'code': 'EN', 'language': 'English'},
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get languages => _languages;
|
||||
|
||||
bool isSelectedLanguage(String title) =>
|
||||
_selectedLanguage['language'] == title;
|
||||
|
||||
Future<void> setSelectedLanguage(
|
||||
{required BuildContext context,
|
||||
required Map<String, dynamic> title}) async {
|
||||
_selectedLanguage = title;
|
||||
|
||||
if (title['code'] == 'አማ') {
|
||||
await setAmharicLanguage(context);
|
||||
} else {
|
||||
await setAmharicLanguage(context);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setAmharicLanguage(BuildContext context) async =>
|
||||
await context.setLocale(const Locale('am'));
|
||||
|
||||
Future<void> setEnglishLanguage(BuildContext context) async =>
|
||||
await context.setLocale(const Locale('en'));
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ String kBaseUrl = 'https://api.yimaruacademy.com';
|
|||
|
||||
String kApiUrl = 'api';
|
||||
|
||||
String kUnitsUrl = 'units';
|
||||
|
||||
String kApiVersionUrl = 'v1';
|
||||
|
||||
String kLevelsUrl = 'levels';
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ enum StateObjects {
|
|||
register,
|
||||
verifyOtp,
|
||||
resendOtp,
|
||||
courseUnits,
|
||||
assessments,
|
||||
startupView,
|
||||
learnLessons,
|
||||
|
|
@ -39,6 +40,7 @@ enum StateObjects {
|
|||
learnCourses,
|
||||
profileImage,
|
||||
learnPrograms,
|
||||
courseModules,
|
||||
courseLessons,
|
||||
profileUpdate,
|
||||
resetPassword,
|
||||
|
|
@ -49,7 +51,6 @@ enum StateObjects {
|
|||
loginWithGoogle,
|
||||
loadLessonVideo,
|
||||
loadCourseVideo,
|
||||
learnSubmodules,
|
||||
requestResetCode,
|
||||
profileCompletion,
|
||||
learnSubscription,
|
||||
|
|
|
|||
182
lib/ui/common/translations/codegen_loader.g.dart
Normal file
182
lib/ui/common/translations/codegen_loader.g.dart
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
|
||||
|
||||
// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart' show AssetLoader;
|
||||
|
||||
class CodegenLoader extends AssetLoader{
|
||||
const CodegenLoader();
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>?> load(String path, Locale locale) {
|
||||
return Future.value(mapLocales[locale.toString()]);
|
||||
}
|
||||
|
||||
static const Map<String,dynamic> _am = {
|
||||
"welcome_back": "እንኳን በደህና ተመለሱ",
|
||||
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ",
|
||||
"email": "ኢሜይል",
|
||||
"password": "የይለፍ ቃል",
|
||||
"forgot_password": "የይለፍ ቃል ረሱ?",
|
||||
"cont": "ቀጥል",
|
||||
"register": "ይመዝገቡ",
|
||||
"login_with_google": "በጉግል ይግቡ",
|
||||
"or": "ወይም",
|
||||
"login_with_phone": "በስልክ ቁጥር ይግቡ",
|
||||
"create_account": "አዲስ መለያ ይፍጠሩ",
|
||||
"already_have_account": "መለያ አለዎት?",
|
||||
"login": " ይግቡ ",
|
||||
"register_with_google": "በጉግል ይመዝገቡ",
|
||||
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
|
||||
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
|
||||
"login_with_email": "በኢሜይል ይግቡ",
|
||||
"create_password": "የይለፍ ቃል ይፍጠሩ",
|
||||
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
|
||||
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
|
||||
"reset_password": " የይለፍ ቃልን ይቀይሩ ",
|
||||
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።",
|
||||
"please_wait": "እባክዎ ይጠብቁ",
|
||||
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል",
|
||||
"reset_code": " የመቀየሪያ ኮድ ",
|
||||
"new_password": "አዲስ የይለፍ ቃል",
|
||||
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
|
||||
"view_course": " ኮርሱን ይመልከቱ ",
|
||||
"take_practice": " ልምምድ ያድርጉ ",
|
||||
"your_current_level": "የአሁኑ ደረጃዎ",
|
||||
"overall_progress": "አጠቃላይ እድገት",
|
||||
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው ",
|
||||
"view_module": "ሞጁሉን ይመልከቱ",
|
||||
"progress": "እድገት",
|
||||
"keep_going": " ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
|
||||
"lessons_in_module": " በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
|
||||
"practice": "ልምምድ",
|
||||
"start": "ጀምር",
|
||||
"in_progress": "በሂደት ላይ",
|
||||
"hello": "ሰላም",
|
||||
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
|
||||
"learn": "ይማሩ ",
|
||||
"course": "ኮርስ",
|
||||
"profile": " ፕሮፋይል ",
|
||||
"speaking_partner": "የንግግር ጓደኛ ",
|
||||
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
|
||||
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
|
||||
"start_practice": "ልምምድ ጀምር",
|
||||
"almost_there": "ሊጨርሱ ተቃርበዋል",
|
||||
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
|
||||
"continue_practice": "ልምምዱን ይቀጥሉ",
|
||||
"end_session": "ክፍለ ጊዜውን ያብቁ ",
|
||||
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
|
||||
"practice_speaking": "ንግግርን ይለማመዱ ",
|
||||
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
|
||||
"reply": "እንደገና አዳምጥ",
|
||||
"cancel": "ይቅር",
|
||||
"you_are_speaking": "እየተናገሩ ነው",
|
||||
"practice_completed": "ልምምዱ ተጠናቅቋል",
|
||||
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
|
||||
"practice_again": "እንደገና ይለማመዱ",
|
||||
"conversation_review": "የንግግር ግምገማ ",
|
||||
"result": "ውጤት",
|
||||
"quick_tip": "ጠቃሚ ምክር",
|
||||
"retry": "እንደገና ይሞክሩ",
|
||||
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
|
||||
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
|
||||
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
|
||||
"hi": "ሰላም",
|
||||
"edit_profile": "መገለጫ ያስተካክሉ",
|
||||
"first_name": "የመጀመሪያ ስም",
|
||||
"last_name": "የአባት ስም",
|
||||
"gender": "ፆታ",
|
||||
"male": "ወንድ",
|
||||
"female": "ሴት",
|
||||
"phone_number": "የስልክ ቁጥር",
|
||||
"country": "ሀገር",
|
||||
"region": "ክልል",
|
||||
"occupation": "የስራ መስክ ",
|
||||
"save_changes": "ለውጦችን ያስቀምጡ"
|
||||
};
|
||||
static const Map<String,dynamic> _en = {
|
||||
"welcome_back": "Welcome back",
|
||||
"dont_have_account": "Don't have an account? Register",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"forgot_password": "Forgot password?",
|
||||
"cont": "Continue",
|
||||
"register": "Register",
|
||||
"login_with_google": "Login with Google",
|
||||
"or": "Or",
|
||||
"login_with_phone": "Login with phone number",
|
||||
"create_account": "Create an account",
|
||||
"already_have_account": "Already have an account?",
|
||||
"login": "Login",
|
||||
"register_with_google": "Register with Google",
|
||||
"register_with_phone": "Register with phone number",
|
||||
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
|
||||
"login_with_email": "Login with email",
|
||||
"create_password": "Create password",
|
||||
"confirm_password": "Confirm password",
|
||||
"sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’",
|
||||
"reset_password": "Reset Password",
|
||||
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
|
||||
"please_wait": "Please wait",
|
||||
"reset_code_sent": "Reset code sent successfully",
|
||||
"reset_code": "Reset code",
|
||||
"new_password": "New password",
|
||||
"logged_in_successfully": "Logged in successfully",
|
||||
"view_course": "View course",
|
||||
"take_practice": "Take practice",
|
||||
"your_current_level": "Your current level",
|
||||
"overall_progress": "Overall progress",
|
||||
"great_work": "Keep up the great work! You're doing amazing",
|
||||
"view_module": "View module",
|
||||
"progress": "Progress",
|
||||
"keep_going": "Let's keep going - you're more than half there",
|
||||
"lessons_in_module": "Lessons in this module",
|
||||
"practice": "Practice",
|
||||
"start": "Start",
|
||||
"in_progress": "In Progress",
|
||||
"hello": "Hello",
|
||||
"ready_to_learn": "Ready to keep learning English today",
|
||||
"learn": "Learn",
|
||||
"course": "Course",
|
||||
"profile": "Profile",
|
||||
"speaking_partner": "Speaking partner",
|
||||
"practice_what_you_learned": "Let's practice what you just learnt",
|
||||
"practice_questions": "I will ask you a few questions and you can respond",
|
||||
"start_practice": "Start practice",
|
||||
"almost_there": "You're almost there",
|
||||
"finish_session": "Finish the session to see your progress",
|
||||
"continue_practice": "Continue practice",
|
||||
"end_session": "End session",
|
||||
"tap_start_to_listen": "Tap the start button to listen",
|
||||
"practice_speaking": "Practice speaking",
|
||||
"tap_microphone": "Tap the microphone to speak",
|
||||
"reply": "Reply",
|
||||
"cancel": "Cancel",
|
||||
"you_are_speaking": "You're speaking",
|
||||
"practice_completed": "Practice completed",
|
||||
"great_improvement": "You sound more confident this time, great improvement",
|
||||
"practice_again": "Practice again",
|
||||
"conversation_review": "Conversation review",
|
||||
"result": "Result",
|
||||
"quick_tip": "Quick tip",
|
||||
"retry": "Retry",
|
||||
"completed_a1": "Yay, you've completed A1",
|
||||
"analyzing_speaking": "We're now analyzing your speaking skill",
|
||||
"view_profile": "View profile",
|
||||
"hi": "Hi",
|
||||
"edit_profile": "Edit profile",
|
||||
"first_name": "First name",
|
||||
"last_name": "Last name",
|
||||
"gender": "Gender",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"phone_number": "Phone number",
|
||||
"country": "Country",
|
||||
"region": "Region",
|
||||
"occupation": "Occupation",
|
||||
"save_changes": "Save changes"
|
||||
};
|
||||
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
|
||||
}
|
||||
85
lib/ui/common/translations/locale_keys.g.dart
Normal file
85
lib/ui/common/translations/locale_keys.g.dart
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
abstract class LocaleKeys {
|
||||
static const welcome_back = 'welcome_back';
|
||||
static const dont_have_account = 'dont_have_account';
|
||||
static const email = 'email';
|
||||
static const password = 'password';
|
||||
static const forgot_password = 'forgot_password';
|
||||
static const cont = 'cont';
|
||||
static const register = 'register';
|
||||
static const login_with_google = 'login_with_google';
|
||||
static const or = 'or';
|
||||
static const login_with_phone = 'login_with_phone';
|
||||
static const create_account = 'create_account';
|
||||
static const already_have_account = 'already_have_account';
|
||||
static const login = 'login';
|
||||
static const register_with_google = 'register_with_google';
|
||||
static const register_with_phone = 'register_with_phone';
|
||||
static const enter_phone_number = 'enter_phone_number';
|
||||
static const login_with_email = 'login_with_email';
|
||||
static const create_password = 'create_password';
|
||||
static const confirm_password = 'confirm_password';
|
||||
static const sign_up_agreement = 'sign_up_agreement';
|
||||
static const reset_password = 'reset_password';
|
||||
static const enter_email_reset_code = 'enter_email_reset_code';
|
||||
static const please_wait = 'please_wait';
|
||||
static const reset_code_sent = 'reset_code_sent';
|
||||
static const reset_code = 'reset_code';
|
||||
static const new_password = 'new_password';
|
||||
static const logged_in_successfully = 'logged_in_successfully';
|
||||
static const view_course = 'view_course';
|
||||
static const take_practice = 'take_practice';
|
||||
static const your_current_level = 'your_current_level';
|
||||
static const overall_progress = 'overall_progress';
|
||||
static const great_work = 'great_work';
|
||||
static const view_module = 'view_module';
|
||||
static const progress = 'progress';
|
||||
static const keep_going = 'keep_going';
|
||||
static const lessons_in_module = 'lessons_in_module';
|
||||
static const practice = 'practice';
|
||||
static const start = 'start';
|
||||
static const in_progress = 'in_progress';
|
||||
static const hello = 'hello';
|
||||
static const ready_to_learn = 'ready_to_learn';
|
||||
static const learn = 'learn';
|
||||
static const course = 'course';
|
||||
static const profile = 'profile';
|
||||
static const speaking_partner = 'speaking_partner';
|
||||
static const practice_what_you_learned = 'practice_what_you_learned';
|
||||
static const practice_questions = 'practice_questions';
|
||||
static const start_practice = 'start_practice';
|
||||
static const almost_there = 'almost_there';
|
||||
static const finish_session = 'finish_session';
|
||||
static const continue_practice = 'continue_practice';
|
||||
static const end_session = 'end_session';
|
||||
static const tap_start_to_listen = 'tap_start_to_listen';
|
||||
static const practice_speaking = 'practice_speaking';
|
||||
static const tap_microphone = 'tap_microphone';
|
||||
static const reply = 'reply';
|
||||
static const cancel = 'cancel';
|
||||
static const you_are_speaking = 'you_are_speaking';
|
||||
static const practice_completed = 'practice_completed';
|
||||
static const great_improvement = 'great_improvement';
|
||||
static const practice_again = 'practice_again';
|
||||
static const conversation_review = 'conversation_review';
|
||||
static const result = 'result';
|
||||
static const quick_tip = 'quick_tip';
|
||||
static const retry = 'retry';
|
||||
static const completed_a1 = 'completed_a1';
|
||||
static const analyzing_speaking = 'analyzing_speaking';
|
||||
static const view_profile = 'view_profile';
|
||||
static const hi = 'hi';
|
||||
static const edit_profile = 'edit_profile';
|
||||
static const first_name = 'first_name';
|
||||
static const last_name = 'last_name';
|
||||
static const gender = 'gender';
|
||||
static const phone_number = 'phone_number';
|
||||
static const country = 'country';
|
||||
static const region = 'region';
|
||||
static const occupation = 'occupation';
|
||||
static const save_changes = 'save_changes';
|
||||
|
||||
}
|
||||
|
|
@ -10,15 +10,25 @@ import '../../../app/app.locator.dart';
|
|||
import '../../../app/app.router.dart';
|
||||
import '../../../models/assessment.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
|
||||
class AssessmentViewModel extends BaseViewModel {
|
||||
class AssessmentViewModel extends ReactiveViewModel {
|
||||
// Dependency injection
|
||||
final _apiService = locator<ApiService>();
|
||||
final _dialogService = locator<DialogService>();
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
final _navigationService = locator<NavigationService>();
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
// In-app navigation
|
||||
int _currentPage = 0;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ class CourseViewModel extends ReactiveViewModel {
|
|||
'description':
|
||||
'Prepare for IELTS, TOEFL, or Duolingo with structured practice.'
|
||||
},
|
||||
{
|
||||
/* {
|
||||
'title': 'Skill-Based Courses',
|
||||
'description':
|
||||
'Learn English for the workplace, travel, and real-life communication.'
|
||||
},
|
||||
},*/
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get courses => _courses;
|
||||
|
|
|
|||
|
|
@ -101,23 +101,25 @@ class CourseCatalogView extends StackedView<CourseCatalogViewModel> {
|
|||
|
||||
Widget _buildListView(CourseCatalogViewModel viewModel) => ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.courseCatalogs.length,
|
||||
itemCount: viewModel.catalogs.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _buildTile(
|
||||
courseCatalog: viewModel.courseCatalogs[index],
|
||||
onCourseTap: () {},
|
||||
onPracticeTap: () {}),
|
||||
onPracticeTap: () {},
|
||||
catalog: viewModel.catalogs[index],
|
||||
onCourseTap: () async =>
|
||||
await viewModel.navigateToCourseUnit(viewModel.catalogs[index]),
|
||||
),
|
||||
separatorBuilder: (context, index) => verticalSpaceSmall,
|
||||
);
|
||||
|
||||
Widget _buildTile({
|
||||
required CourseCatalog courseCatalog,
|
||||
required CourseCatalog catalog,
|
||||
required GestureTapCallback onCourseTap,
|
||||
required GestureTapCallback onPracticeTap,
|
||||
}) =>
|
||||
CourseCatalogTile(
|
||||
catalog: catalog,
|
||||
onCourseTap: onCourseTap,
|
||||
onPracticeTap: onPracticeTap,
|
||||
courseCatalog: courseCatalog,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,25 @@ import 'package:yimaru_app/models/course_catalog.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
import '../../../app/app.router.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
import '../../../services/course_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
import '../../common/enmus.dart';
|
||||
|
||||
class CourseCatalogViewModel extends BaseViewModel {
|
||||
class CourseCatalogViewModel extends ReactiveViewModel {
|
||||
// Dependency injection
|
||||
final _apiService = locator<ApiService>();
|
||||
final _courseService = locator<CourseService>();
|
||||
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// Course catalogs
|
||||
List<CourseCatalog> _courseCatalogs = [];
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_courseService];
|
||||
|
||||
List<CourseCatalog> get courseCatalogs => _courseCatalogs;
|
||||
// Learn lessons
|
||||
List<CourseCatalog> get _catalogs => _courseService.catalogs;
|
||||
|
||||
List<CourseCatalog> get catalogs => _catalogs;
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
|
@ -27,19 +31,19 @@ class CourseCatalogViewModel extends BaseViewModel {
|
|||
Future<void> navigateToCoursePractice(int id) async =>
|
||||
_navigationService.navigateToCoursePracticeView(id: id);
|
||||
|
||||
// Future<void> navigateToSubcourse(Subcategory subcategory) async =>
|
||||
// _navigationService.navigateToCourseView(subcategory: subcategory);
|
||||
Future<void> navigateToCourseUnit(CourseCatalog catalog) async =>
|
||||
await _navigationService.navigateToCourseUnitView(catalog: catalog);
|
||||
|
||||
// Remote api call
|
||||
|
||||
// Course catalogs
|
||||
Future<void> getCourseCatalogs() async =>
|
||||
await runBusyFuture(_getSubcategories(),
|
||||
await runBusyFuture(_getCourseCatalogs(),
|
||||
busyObject: StateObjects.courseCatalogs);
|
||||
|
||||
Future<void> _getSubcategories() async {
|
||||
Future<void> _getCourseCatalogs() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
_courseCatalogs = await _apiService.getCourseCatalogs();
|
||||
await _courseService.getCourseCatalogs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
|
|||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
import '../../../app/app.router.dart';
|
||||
import '../../../models/option.dart';
|
||||
import '../../../models/assessment_question.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
|
|
@ -153,6 +154,8 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
|
|||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
|
||||
|
||||
// Remote api call
|
||||
|
||||
// Question navigation
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ class QuestionLoadingScreen extends StatelessWidget {
|
|||
Widget _buildAppBar() => LargeAppBar(
|
||||
onPop: onPop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
showLanguageSelection: false,
|
||||
|
||||
);
|
||||
|
||||
Widget _buildBody() => Expanded(child: Container());
|
||||
|
|
|
|||
|
|
@ -1,29 +1,148 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/models/course_catalog.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_unit_tile.dart';
|
||||
|
||||
import '../../../models/course_unit.dart';
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/enmus.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
import '../../widgets/course_module_banner.dart';
|
||||
import '../../widgets/custom_circular_progress_indicator.dart';
|
||||
import '../../widgets/custom_elevated_button.dart';
|
||||
import '../../widgets/overall_progress.dart';
|
||||
import '../../widgets/small_app_bar.dart';
|
||||
import 'course_unit_viewmodel.dart';
|
||||
|
||||
class CourseUnitView extends StackedView<CourseUnitViewModel> {
|
||||
const CourseUnitView({Key? key}) : super(key: key);
|
||||
final CourseCatalog catalog;
|
||||
|
||||
const CourseUnitView({Key? key, required this.catalog}) : super(key: key);
|
||||
|
||||
@override
|
||||
void onViewModelReady(CourseUnitViewModel viewModel) async {
|
||||
await viewModel.getCourseUnits(catalog.id ?? 0);
|
||||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
||||
@override
|
||||
CourseUnitViewModel viewModelBuilder(BuildContext context) =>
|
||||
CourseUnitViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
CourseUnitViewModel viewModel,
|
||||
Widget? child,
|
||||
) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
body: Container(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0),
|
||||
child: const Center(child: Text("CourseUnitView")),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
CourseUnitViewModel viewModelBuilder(
|
||||
BuildContext context,
|
||||
) =>
|
||||
CourseUnitViewModel();
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(CourseUnitViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(CourseUnitViewModel viewModel) =>
|
||||
SafeArea(child: _buildBody(viewModel));
|
||||
|
||||
Widget _buildBody(CourseUnitViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(CourseUnitViewModel viewModel) => Column(
|
||||
children: [
|
||||
verticalSpaceMedium,
|
||||
_buildAppBar(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildModulesColumnWrapper(viewModel),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildAppBar(CourseUnitViewModel viewModel) => SmallAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
title: 'Course Detail',
|
||||
);
|
||||
|
||||
Widget _buildModulesColumnWrapper(CourseUnitViewModel viewModel) =>
|
||||
Expanded(child: _buildLevelsColumnScrollView(viewModel));
|
||||
|
||||
Widget _buildLevelsColumnScrollView(CourseUnitViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildLevelsColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildLevelsColumn(CourseUnitViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: _buildLevelsColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildLevelsColumnChildren(CourseUnitViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildCourseModuleBanner(),
|
||||
verticalSpaceMedium,
|
||||
_buildOverallProgress(),
|
||||
verticalSpaceTiny,
|
||||
_buildContinueButton(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildListViewBuilder(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
catalog.name ?? '',
|
||||
style: style18P600,
|
||||
);
|
||||
|
||||
Widget _buildCourseModuleBanner() => const CourseModuleBanner();
|
||||
|
||||
Widget _buildOverallProgress() => const OverallProgress(
|
||||
progress: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
indicatorBackgroundColor: kcVeryLightGrey,
|
||||
);
|
||||
|
||||
Widget _buildContinueButton(CourseUnitViewModel viewModel) =>
|
||||
const CustomElevatedButton(
|
||||
height: 55,
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
text: 'Continue Course',
|
||||
backgroundColor: kcPrimaryColor);
|
||||
|
||||
Widget _buildListViewBuilder(CourseUnitViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.courseUnits)
|
||||
? _buildProgressIndicator()
|
||||
: _buildListView(viewModel);
|
||||
|
||||
Widget _buildProgressIndicator() => const Center(
|
||||
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||
);
|
||||
|
||||
Widget _buildListView(CourseUnitViewModel viewModel) => ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.units.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _buildTile(
|
||||
index: index,
|
||||
unit: viewModel.units[index],
|
||||
onPracticeTap: () {},
|
||||
onLessonTap: () {}),
|
||||
);
|
||||
|
||||
Widget _buildTile({
|
||||
required int index,
|
||||
required CourseUnit unit,
|
||||
required GestureTapCallback onLessonTap,
|
||||
required GestureTapCallback onPracticeTap,
|
||||
}) =>
|
||||
CourseUnitTile(
|
||||
unit: unit,
|
||||
index: index,
|
||||
onLessonTap: onLessonTap,
|
||||
onPracticeTap: onPracticeTap,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,55 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/models/course_module.dart';
|
||||
|
||||
class CourseUnitViewModel extends BaseViewModel {}
|
||||
import '../../../app/app.locator.dart';
|
||||
import '../../../models/course_unit.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
import '../../../services/course_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
import '../../common/enmus.dart';
|
||||
|
||||
class CourseUnitViewModel extends ReactiveViewModel {
|
||||
// Dependency injection
|
||||
final _courseService = locator<CourseService>();
|
||||
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_courseService];
|
||||
|
||||
// Course units
|
||||
List<CourseUnit> get _units => _courseService.units;
|
||||
|
||||
List<CourseUnit> get units => _units;
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
// Remote api call
|
||||
|
||||
// Course units
|
||||
Future<void> getCourseUnits(int id) async =>
|
||||
await runBusyFuture(_getCourseUnits(id),
|
||||
busyObject: StateObjects.courseUnits);
|
||||
|
||||
Future<void> _getCourseUnits(int id) async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
await _courseService.getCourseUnits(id);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getCourseUnitModules(
|
||||
{required int id, required int index}) async =>
|
||||
await runBusyFuture(_getCourseUnitModules(id: id, index: index),
|
||||
busyObject: StateObjects.courseModules);
|
||||
|
||||
Future<void> _getCourseUnitModules(
|
||||
{required int id, required int index}) async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
await _courseService.getCourseUnitModule(id: id, index: index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,30 @@ import 'package:stacked_services/stacked_services.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
import '../../../app/app.router.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
import '../../common/enmus.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
|
||||
class ForgetPasswordViewModel extends FormViewModel {
|
||||
class ForgetPasswordViewModel extends ReactiveViewModel
|
||||
with FormStateHelper
|
||||
implements FormViewModel {
|
||||
final _apiService = locator<ApiService>();
|
||||
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
// User data
|
||||
final Map<String, dynamic> _userData = {};
|
||||
|
||||
|
|
@ -165,6 +178,10 @@ class ForgetPasswordViewModel extends FormViewModel {
|
|||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
Future<void> navigateToLanguage() async =>
|
||||
await _navigationService.navigateToLanguageView();
|
||||
|
||||
|
||||
Future<void> replaceWithLogin() async =>
|
||||
await _navigationService.clearStackAndShow(Routes.loginView);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _inAppPop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _inAppPop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -22,48 +22,72 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
|||
LanguageViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildScaffoldWrapper(viewModel);
|
||||
_buildScaffoldWrapper(context: context, viewModel: viewModel);
|
||||
|
||||
Widget _buildScaffoldWrapper(LanguageViewModel viewModel) => Scaffold(
|
||||
Widget _buildScaffoldWrapper(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffold(viewModel),
|
||||
body: _buildScaffold(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffold(LanguageViewModel viewModel) =>
|
||||
SafeArea(child: _buildBodyWrapper(viewModel));
|
||||
Widget _buildScaffold(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
SafeArea(
|
||||
child: _buildBodyWrapper(context: context, viewModel: viewModel));
|
||||
|
||||
Widget _buildBodyWrapper(LanguageViewModel viewModel) => Column(
|
||||
Widget _buildBodyWrapper(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildBodyChildren(viewModel),
|
||||
children: _buildBodyChildren(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildBodyChildren(LanguageViewModel viewModel) => [
|
||||
List<Widget> _buildBodyChildren(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
[
|
||||
verticalSpaceMedium,
|
||||
_buildAppBarWrapper(viewModel),
|
||||
_buildExpandedBody(viewModel)
|
||||
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||
];
|
||||
|
||||
Widget _buildExpandedBody(LanguageViewModel viewModel) =>
|
||||
Expanded(child: _buildColumnWrapper(viewModel));
|
||||
Widget _buildExpandedBody(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
Expanded(
|
||||
child: _buildColumnWrapper(context: context, viewModel: viewModel));
|
||||
|
||||
Widget _buildColumnWrapper(LanguageViewModel viewModel) => Padding(
|
||||
Widget _buildColumnWrapper(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildColumn(viewModel),
|
||||
child: _buildColumn(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildColumn(LanguageViewModel viewModel) => Column(
|
||||
Widget _buildColumn(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildColumnChildren(viewModel),
|
||||
children: _buildColumnChildren(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildColumnChildren(LanguageViewModel viewModel) => [
|
||||
List<Widget> _buildColumnChildren(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
[
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildLanguages(viewModel)
|
||||
_buildLanguages(context: context, viewModel: viewModel)
|
||||
];
|
||||
|
||||
Widget _buildAppBarWrapper(LanguageViewModel viewModel) => Padding(
|
||||
|
|
@ -87,16 +111,19 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
|||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildLanguages(LanguageViewModel viewModel) => ListView.builder(
|
||||
Widget _buildLanguages(
|
||||
{required BuildContext context,
|
||||
required LanguageViewModel viewModel}) =>
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: viewModel.languages.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _buildLanguage(
|
||||
title: viewModel.languages[index]['language'],
|
||||
onTap: () => viewModel.setSelectedLanguage(
|
||||
context: context, title: viewModel.languages[index]),
|
||||
selected: viewModel
|
||||
.isSelectedLanguage(viewModel.languages[index]['language']),
|
||||
onTap: () =>
|
||||
viewModel.setSelectedLanguage(viewModel.languages[index]),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,38 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/services/localization_service.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
|
||||
class LanguageViewModel extends BaseViewModel {
|
||||
class LanguageViewModel extends ReactiveViewModel {
|
||||
// Dependency injection
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_localizationService];
|
||||
|
||||
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> _selectedLanguage = {
|
||||
'code': 'EN',
|
||||
'language': 'English'
|
||||
};
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
final List<Map<String, dynamic>> _languages = [
|
||||
{'code': 'አማ', 'language': 'አማርኛ'},
|
||||
{'code': 'EN', 'language': 'English'},
|
||||
];
|
||||
List<Map<String, dynamic>> get _languages => _localizationService.languages;
|
||||
|
||||
List<Map<String, dynamic>> get languages => _languages;
|
||||
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
// Languages
|
||||
bool isSelectedLanguage(String title) =>
|
||||
_selectedLanguage['language'] == title;
|
||||
_localizationService.isSelectedLanguage(title);
|
||||
|
||||
void setSelectedLanguage(Map<String, dynamic> title) {
|
||||
_selectedLanguage = title;
|
||||
rebuildUi();
|
||||
}
|
||||
Future<void> setSelectedLanguage(
|
||||
{required BuildContext context,
|
||||
required Map<String, dynamic> title}) async =>
|
||||
await _localizationService.setSelectedLanguage(
|
||||
context: context, title: title);
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/models/learn_module.dart';
|
||||
import 'package:yimaru_app/ui/widgets/learn_module_tile.dart';
|
||||
import 'package:yimaru_app/ui/widgets/overall_learn_progress.dart';
|
||||
import 'package:yimaru_app/ui/widgets/overall_progress.dart';
|
||||
|
||||
import '../../../models/learn_course.dart';
|
||||
import '../../common/app_colors.dart';
|
||||
|
|
@ -96,7 +96,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
|||
style: style14P400,
|
||||
);
|
||||
|
||||
Widget _buildOverallProgress() => OverallLearnProgress(
|
||||
Widget _buildOverallProgress() => OverallProgress(
|
||||
indicatorBackgroundColor: kcWhite,
|
||||
progress: course.access?.progressPercent ?? 0,
|
||||
backgroundColor: kcPrimaryColor.withOpacity(0.1),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:yimaru_app/models/user.dart';
|
|||
import '../../../services/api_service.dart';
|
||||
import '../../../services/authentication_service.dart';
|
||||
import '../../../services/google_auth_service.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
import '../../common/enmus.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
|
|
@ -24,16 +25,23 @@ class LoginViewModel extends ReactiveViewModel
|
|||
|
||||
final _googleAuthService = locator<GoogleAuthService>();
|
||||
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
final _authenticationService = locator<AuthenticationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||
|
||||
// Google user
|
||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||
|
||||
GoogleSignInAccount? get googleUser => _googleUser;
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
// In-app navigation
|
||||
int _currentPage = 0;
|
||||
|
||||
|
|
@ -158,9 +166,14 @@ class LoginViewModel extends ReactiveViewModel
|
|||
Future<void> navigateToRegister() async =>
|
||||
await _navigationService.navigateToRegisterView();
|
||||
|
||||
Future<void> navigateToLanguage() async =>
|
||||
await _navigationService.navigateToLanguageView();
|
||||
|
||||
Future<void> navigateToForgetPassword() async =>
|
||||
await _navigationService.navigateToForgetPasswordView();
|
||||
|
||||
|
||||
|
||||
Future<void> replaceWithStartUp() async =>
|
||||
await _navigationService.clearStackAndShow(Routes.startupView);
|
||||
|
||||
|
|
|
|||
|
|
@ -80,9 +80,13 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
|
|||
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||
];
|
||||
|
||||
Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar(
|
||||
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: false,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
|
||||
import 'package:yimaru_app/ui/views/login/login_view.form.dart';
|
||||
import 'package:yimaru_app/ui/widgets/obscure_password.dart';
|
||||
|
||||
|
|
@ -76,9 +78,11 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
_buildExpandedBody(context: context, viewModel: viewModel)
|
||||
];
|
||||
|
||||
Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar(
|
||||
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: false,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
@ -140,7 +144,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Welcome Back',
|
||||
LocaleKeys.welcome_back.tr(),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
|
|
@ -150,10 +154,10 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
|
||||
Widget _buildEmailFormField(LoginViewModel viewModel) => TextFormField(
|
||||
controller: emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onTap: viewModel.setEmailFocus,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: inputDecoration(
|
||||
hint: 'Email',
|
||||
hint: LocaleKeys.email.tr(),
|
||||
focus: viewModel.focusEmail,
|
||||
filled: emailController.text.isNotEmpty),
|
||||
);
|
||||
|
|
@ -173,7 +177,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
onTap: viewModel.setPasswordFocus,
|
||||
obscureText: viewModel.obscurePassword,
|
||||
decoration: inputDecoration(
|
||||
hint: 'Password',
|
||||
hint: LocaleKeys.password.tr(),
|
||||
focus: viewModel.focusPassword,
|
||||
suffix: _buildObscureButton(viewModel),
|
||||
filled: passwordController.text.isNotEmpty),
|
||||
|
|
@ -207,7 +211,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
);
|
||||
|
||||
Widget _buildForgetPasswordText() => Text(
|
||||
'Forgot Password?',
|
||||
LocaleKeys.forgot_password.tr(),
|
||||
style: style14P400,
|
||||
);
|
||||
|
||||
|
|
@ -227,9 +231,9 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
Widget _buildContinueButton(LoginViewModel viewModel) => CustomElevatedButton(
|
||||
height: 55,
|
||||
safe: false,
|
||||
text: 'Continue',
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
text: LocaleKeys.cont.tr(),
|
||||
onTap: emailController.text.isNotEmpty &&
|
||||
passwordController.text.isNotEmpty
|
||||
? () async => await _login(viewModel)
|
||||
|
|
@ -245,9 +249,9 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
height: 55,
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
text: 'Login with Google',
|
||||
borderColor: kcPrimaryColor,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
text: LocaleKeys.login_with_google.tr(),
|
||||
leadingImage: 'assets/icons/google.png',
|
||||
onTap: () async => await viewModel.signInWithGoogle(),
|
||||
);
|
||||
|
|
@ -263,7 +267,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
borderColor: kcPrimaryColor,
|
||||
onTap: () => viewModel.goTo(1),
|
||||
foregroundColor: kcPrimaryColor,
|
||||
text: 'Login with Phone Number',
|
||||
text: LocaleKeys.login_with_phone.tr()
|
||||
);
|
||||
|
||||
Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => viewModel.goTo(0),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:stacked_services/stacked_services.dart';
|
|||
import 'package:yimaru_app/app/app.router.dart';
|
||||
import '../../../app/app.locator.dart';
|
||||
import '../../../services/google_auth_service.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
|
||||
class OnboardingViewModel extends ReactiveViewModel
|
||||
with FormStateHelper
|
||||
|
|
@ -14,14 +15,24 @@ class OnboardingViewModel extends ReactiveViewModel
|
|||
|
||||
final _googleAuthService = locator<GoogleAuthService>();
|
||||
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||
|
||||
// Google user
|
||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||
|
||||
GoogleSignInAccount? get googleUser => _googleUser;
|
||||
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
// Navigation
|
||||
int _currentPage = 0;
|
||||
|
||||
|
|
@ -207,20 +218,6 @@ class OnboardingViewModel extends ReactiveViewModel
|
|||
|
||||
List<String> get topics => _topics;
|
||||
|
||||
// Languages
|
||||
final List<Map<String, dynamic>> _languages = [
|
||||
{'code': 'አማ', 'language': 'አማርኛ'},
|
||||
{'code': 'EN', 'language': 'English'},
|
||||
];
|
||||
|
||||
List<Map<String, dynamic>> get languages => _languages;
|
||||
|
||||
Map<String, dynamic> _selectedLanguage = {
|
||||
'code': 'EN',
|
||||
'language': 'English'
|
||||
};
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
// User data
|
||||
final Map<String, dynamic> _userData = {};
|
||||
|
|
@ -548,15 +545,6 @@ class OnboardingViewModel extends ReactiveViewModel
|
|||
|
||||
bool isSelectedTopic(String value) => _selectedTopic == value;
|
||||
|
||||
// Language
|
||||
void setSelectedLanguage(Map<String, dynamic> value) {
|
||||
_selectedLanguage = value;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
bool isSelectedLanguage(String value) =>
|
||||
_selectedLanguage['language'] == value;
|
||||
|
||||
// Add user data
|
||||
void addUserData(Map<String, dynamic> data) {
|
||||
_userData.addAll(data);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class EducationalBackgroundFormScreen
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: false,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
|||
import '../../../app/app.locator.dart';
|
||||
import '../../../models/user.dart';
|
||||
import '../../../services/google_auth_service.dart';
|
||||
import '../../../services/localization_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
|
||||
class RegisterViewModel extends ReactiveViewModel
|
||||
|
|
@ -24,16 +25,24 @@ class RegisterViewModel extends ReactiveViewModel
|
|||
|
||||
final _googleAuthService = locator<GoogleAuthService>();
|
||||
|
||||
final _localizationService = locator<LocalizationService>();
|
||||
|
||||
final _authenticationService = locator<AuthenticationService>();
|
||||
|
||||
@override
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService];
|
||||
List<ListenableServiceMixin> get listenableServices => [_googleAuthService,_localizationService];
|
||||
|
||||
// Google user
|
||||
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
|
||||
|
||||
GoogleSignInAccount? get googleUser => _googleUser;
|
||||
|
||||
// Languages
|
||||
Map<String, dynamic> get _selectedLanguage => _localizationService.selectedLanguage;
|
||||
|
||||
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
|
||||
|
||||
|
||||
// Navigation
|
||||
int _currentPage = 0;
|
||||
|
||||
|
|
@ -260,14 +269,19 @@ class RegisterViewModel extends ReactiveViewModel
|
|||
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
Future<void> navigateToTermsAndConditions() async =>
|
||||
await _navigationService.navigateToTermsAndConditionsView();
|
||||
Future<void> replaceToLogin() async =>
|
||||
await _navigationService.replaceWithLoginView();
|
||||
|
||||
Future<void> navigateToLanguage() async =>
|
||||
await _navigationService.navigateToLanguageView();
|
||||
|
||||
|
||||
Future<void> navigateToPrivacyPolicy() async =>
|
||||
await _navigationService.navigateToPrivacyPolicyView();
|
||||
|
||||
Future<void> replaceToLogin() async =>
|
||||
await _navigationService.replaceWithLoginView();
|
||||
|
||||
Future<void> navigateToTermsAndConditions() async =>
|
||||
await _navigationService.navigateToTermsAndConditionsView();
|
||||
|
||||
Future<void> replaceWithStartUp() async =>
|
||||
await _navigationService.clearStackAndShow(Routes.startupView);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -84,6 +84,10 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
|
||||
onLanguage: ()async => await viewModel.navigateToLanguage(),
|
||||
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
language: viewModel.selectedLanguage['code'],
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(
|
||||
|
|
|
|||
|
|
@ -47,16 +47,20 @@ class StartupViewModel extends ReactiveViewModel {
|
|||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
Future<void> replaceWithHome() async =>
|
||||
await _navigationService.replaceWithHomeView();
|
||||
|
||||
Future<void> replaceWithFailure() async =>
|
||||
await _navigationService.replaceWithFailureView(
|
||||
label: 'Check you internet connection',
|
||||
onTap: () async => await _getProfileStatus());
|
||||
|
||||
|
||||
|
||||
Future<void> replaceWithOnboarding() async =>
|
||||
await _navigationService.replaceWithOnboardingView();
|
||||
|
||||
Future<void> replaceWithHome() async =>
|
||||
await _navigationService.replaceWithHomeView();
|
||||
|
||||
// Remote api calls
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class AssessmentLoadingScreen extends StatelessWidget {
|
|||
Widget _buildAppBar() => LargeAppBar(
|
||||
onPop: onPop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
showLanguageSelection: false,
|
||||
);
|
||||
|
||||
Widget _buildBody() => Expanded(child: Container());
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import '../common/ui_helpers.dart';
|
|||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CourseCatalogTile extends StatelessWidget {
|
||||
final CourseCatalog courseCatalog;
|
||||
final CourseCatalog catalog;
|
||||
final GestureTapCallback? onCourseTap;
|
||||
final GestureTapCallback? onPracticeTap;
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ class CourseCatalogTile extends StatelessWidget {
|
|||
super.key,
|
||||
this.onCourseTap,
|
||||
this.onPracticeTap,
|
||||
required this.courseCatalog,
|
||||
required this.catalog,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -57,7 +57,7 @@ class CourseCatalogTile extends StatelessWidget {
|
|||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
courseCatalog.name ?? '',
|
||||
catalog.name ?? '',
|
||||
style: style16P600,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import 'package:yimaru_app/ui/common/app_colors.dart';
|
|||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||
|
||||
class CourseTopicTile extends StatelessWidget {
|
||||
class CourseModuleTileSmall extends StatelessWidget {
|
||||
final String title;
|
||||
final ProgressStatuses status;
|
||||
|
||||
const CourseTopicTile({super.key, required this.title, required this.status});
|
||||
const CourseModuleTileSmall(
|
||||
{super.key, required this.title, required this.status});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildTile();
|
||||
|
|
@ -27,7 +28,9 @@ class CourseTopicTile extends StatelessWidget {
|
|||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: style16DG600,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: style14DG600,
|
||||
);
|
||||
|
||||
Widget _buildLeadingWrapper() => CircleAvatar(
|
||||
267
lib/ui/widgets/course_unit_tile.dart
Normal file
267
lib/ui/widgets/course_unit_tile.dart
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/models/course_unit.dart';
|
||||
import 'package:yimaru_app/models/submodule.dart';
|
||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/widgets/course_module_tile_small.dart';
|
||||
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
|
||||
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
|
||||
|
||||
import '../common/app_colors.dart';
|
||||
import '../common/ui_helpers.dart';
|
||||
import '../views/course_unit/course_unit_viewmodel.dart';
|
||||
import 'custom_circular_progress_indicator.dart';
|
||||
import 'custom_elevated_button.dart';
|
||||
|
||||
class CourseUnitTile extends ViewModelWidget<CourseUnitViewModel> {
|
||||
final int index;
|
||||
final CourseUnit unit;
|
||||
final GestureTapCallback? onLessonTap;
|
||||
final GestureTapCallback? onPracticeTap;
|
||||
|
||||
const CourseUnitTile({
|
||||
super.key,
|
||||
this.onLessonTap,
|
||||
this.onPracticeTap,
|
||||
required this.unit,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
Future<void> _getCourseModules({
|
||||
required bool expanded,
|
||||
required CourseUnitViewModel viewModel,
|
||||
}) async {
|
||||
if (!expanded) return;
|
||||
|
||||
// Prevent duplicate API calls
|
||||
if ((unit.modules?.isNotEmpty ?? false)) return;
|
||||
|
||||
await viewModel.getCourseUnitModules(index: index, id: unit.id ?? 0);
|
||||
}
|
||||
|
||||
Future<void> _showSheet(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) async =>
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: kcTransparent,
|
||||
builder: (_) => _buildSheet(viewModel),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, CourseUnitViewModel viewModel) =>
|
||||
_buildExpansionTileCard(context: context, viewModel: viewModel);
|
||||
|
||||
Widget _buildExpansionTileCard(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(color: kcVeryLightGrey),
|
||||
),
|
||||
child: _buildTileStack(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildTileStack(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Stack(
|
||||
children: [
|
||||
_buildExpansionTile(context: context, viewModel: viewModel),
|
||||
// _buildContainerShaderState()
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildExpansionTile(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
ExpansionTile(
|
||||
enabled: true,
|
||||
title: _buildTitle(),
|
||||
textColor: kcDarkGrey,
|
||||
showTrailingIcon: true,
|
||||
initiallyExpanded: false,
|
||||
subtitle: _buildSubtitle(),
|
||||
// key: Key(unit.id.toString()),
|
||||
collapsedIconColor: kcDarkGrey,
|
||||
collapsedTextColor: kcDarkGrey,
|
||||
backgroundColor: kcBackgroundColor,
|
||||
shape: Border.all(color: kcTransparent),
|
||||
expandedAlignment: Alignment.centerLeft,
|
||||
collapsedBackgroundColor: kcBackgroundColor,
|
||||
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
onExpansionChanged: (bool expanded) async => await _getCourseModules(
|
||||
expanded: expanded,
|
||||
viewModel: viewModel,
|
||||
),
|
||||
// enabled: status != ProgressStatuses.pending,
|
||||
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
|
||||
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
|
||||
children:
|
||||
_buildExpansionTileChildren(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
unit.name ?? '',
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: style16P600,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'0% completed',
|
||||
style: style14DG500,
|
||||
);
|
||||
|
||||
List<Widget> _buildExpansionTileChildren(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
[_buildExpansionTileItem(context: context, viewModel: viewModel)];
|
||||
|
||||
Widget _buildExpansionTileItem(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildExpansionTileItemChildren(
|
||||
context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildExpansionTileItemChildren(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
[
|
||||
_buildProgressRowWrapper(),
|
||||
verticalSpaceSmall,
|
||||
_buildActionButtonWrapper(context: context, viewModel: viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildCourseModulesState(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildProgressRowWrapper() => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildProgressRow(),
|
||||
);
|
||||
|
||||
Widget _buildProgressRow() => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _buildProgressChildren(),
|
||||
);
|
||||
|
||||
List<Widget> _buildProgressChildren() =>
|
||||
[_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()];
|
||||
|
||||
Widget _buildProgressStatusWrapper() => Expanded(
|
||||
child: _buildProgressStatus(),
|
||||
);
|
||||
|
||||
Widget _buildProgressStatus() => const CustomLinearProgressIndicator(
|
||||
progress: 0,
|
||||
activeColor: kcPrimaryColor,
|
||||
backgroundColor: kcVeryLightGrey);
|
||||
|
||||
Widget _buildProgress() => const Text(
|
||||
'0/0',
|
||||
style: TextStyle(color: kcDarkGrey),
|
||||
);
|
||||
|
||||
Widget _buildActionButtonWrapper(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Container(
|
||||
height: 40,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildActionButtons(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildActionButtons(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Row(
|
||||
children: [
|
||||
_buildLessonButtonWrapper(viewModel),
|
||||
horizontalSpaceSmall,
|
||||
_buildPracticeButtonWrapper(context: context, viewModel: viewModel),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _buildLessonButtonWrapper(CourseUnitViewModel viewModel) => Expanded(
|
||||
child: _buildLessonButton(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildLessonButton(CourseUnitViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
height: 15,
|
||||
borderRadius: 12,
|
||||
onTap: onLessonTap,
|
||||
text: 'View Module',
|
||||
foregroundColor: kcWhite,
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
Widget _buildPracticeButtonWrapper(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
Expanded(
|
||||
child: _buildPracticeButton(context: context, viewModel: viewModel),
|
||||
);
|
||||
|
||||
Widget _buildPracticeButton(
|
||||
{required BuildContext context,
|
||||
required CourseUnitViewModel viewModel}) =>
|
||||
CustomElevatedButton(
|
||||
height: 15,
|
||||
borderRadius: 12,
|
||||
onTap: onPracticeTap,
|
||||
text: 'View Practices',
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
Widget _buildSheet(CourseUnitViewModel viewModel) => FinishPracticeSheet(
|
||||
onTap: viewModel.pop,
|
||||
);
|
||||
|
||||
Widget _buildCourseModulesState(CourseUnitViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.courseModules)
|
||||
? _buildProgressIndicator()
|
||||
: _buildCourseModules(viewModel);
|
||||
|
||||
Widget _buildProgressIndicator() => const Center(
|
||||
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
|
||||
);
|
||||
|
||||
Widget _buildCourseModules(CourseUnitViewModel viewModel) => ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: unit.modules?.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) =>
|
||||
_buildCourseModuleCard(unit.modules?[index].name ?? ''),
|
||||
);
|
||||
|
||||
Widget _buildCourseModuleCard(String title) =>
|
||||
CourseModuleTileSmall(title: title, status: ProgressStatuses.completed);
|
||||
|
||||
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending
|
||||
// ? _buildContainerShaderWrapper()
|
||||
// : Container();
|
||||
|
||||
Widget _buildContainerShaderWrapper() => Positioned.fill(
|
||||
child: _buildContainerShader(),
|
||||
);
|
||||
|
||||
Widget _buildContainerShader() => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: kcWhite.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart';
|
|||
import 'package:yimaru_app/ui/widgets/language_button.dart';
|
||||
|
||||
class LargeAppBar extends StatelessWidget {
|
||||
final String? language;
|
||||
final bool showBackButton;
|
||||
final GestureTapCallback? onPop;
|
||||
final bool showLanguageSelection;
|
||||
|
|
@ -14,6 +15,7 @@ class LargeAppBar extends StatelessWidget {
|
|||
{super.key,
|
||||
this.onPop,
|
||||
this.onClose,
|
||||
this.language,
|
||||
this.onLanguage,
|
||||
required this.showBackButton,
|
||||
required this.showLanguageSelection});
|
||||
|
|
@ -72,8 +74,8 @@ class LargeAppBar extends StatelessWidget {
|
|||
: _buildCloseButton());
|
||||
|
||||
Widget _buildLanguageSelector() => LanguageButton(
|
||||
language: 'EN',
|
||||
onTap: onLanguage,
|
||||
language: language ?? '',
|
||||
);
|
||||
|
||||
Widget _buildCloseButton() => IconButton(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../common/app_colors.dart';
|
||||
import '../common/translations/locale_keys.g.dart';
|
||||
import '../common/ui_helpers.dart';
|
||||
|
||||
class OptionTextDivider extends StatelessWidget {
|
||||
|
|
@ -22,9 +24,9 @@ class OptionTextDivider extends StatelessWidget {
|
|||
|
||||
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
|
||||
|
||||
Widget _buildOrText() => const Text(
|
||||
'or',
|
||||
Widget _buildOrText() => Text(
|
||||
LocaleKeys.or.tr(),
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ 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_linear_progress_indicator.dart';
|
||||
|
||||
class OverallLearnProgress extends StatelessWidget {
|
||||
class OverallProgress extends StatelessWidget {
|
||||
final int progress;
|
||||
final Color backgroundColor;
|
||||
final Color indicatorBackgroundColor;
|
||||
const OverallLearnProgress(
|
||||
const OverallProgress(
|
||||
{super.key,
|
||||
required this.progress,
|
||||
required this.backgroundColor,
|
||||
|
|
@ -63,8 +63,8 @@ class OverallLearnProgress extends StatelessWidget {
|
|||
backgroundColor: indicatorBackgroundColor,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Keep up the great work! You\'re doing amazing.',
|
||||
style: TextStyle(color: kcDarkGrey),
|
||||
style: style14DG500,
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
|
||||
|
||||
import '../common/app_colors.dart';
|
||||
import '../common/ui_helpers.dart';
|
||||
|
|
@ -18,9 +20,9 @@ class RegisterForAccount extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
|
||||
Widget _buildLeadingText() => const Text(
|
||||
'Don’t have an account? ',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
Widget _buildLeadingText() => Text(
|
||||
'${LocaleKeys.dont_have_account.tr()} ',
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildRegisterTextButton() => TextButton(
|
||||
|
|
@ -31,8 +33,8 @@ class RegisterForAccount extends StatelessWidget {
|
|||
child: _buildRegisterText(),
|
||||
);
|
||||
|
||||
Widget _buildRegisterText() => const Text(
|
||||
'Register',
|
||||
style: TextStyle(color: kcPrimaryColor),
|
||||
Widget _buildRegisterText() => Text(
|
||||
LocaleKeys.register.tr(),
|
||||
style:style14P400 ,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import flutter_secure_storage_darwin
|
|||
import google_sign_in_ios
|
||||
import package_info_plus
|
||||
import record_macos
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
import video_player_avfoundation
|
||||
|
|
@ -35,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||
|
|
|
|||
77
pubspec.lock
77
pubspec.lock
|
|
@ -377,6 +377,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_localization
|
||||
sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.8"
|
||||
easy_logger:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: easy_logger
|
||||
sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.2"
|
||||
email_validator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -654,6 +670,11 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_native_splash:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1472,6 +1493,62 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shared_preferences:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.23"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name: yimaru_app
|
||||
version: 0.1.15+17
|
||||
version: 0.1.16+18
|
||||
publish_to: 'none'
|
||||
description: A new Flutter project.
|
||||
|
||||
|
|
@ -9,6 +9,7 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
http: any
|
||||
intl: any
|
||||
dio: ^5.9.0
|
||||
path: ^1.9.1
|
||||
|
|
@ -37,6 +38,7 @@ dependencies:
|
|||
json_annotation: ^4.9.0
|
||||
flutter_spinkit: ^5.2.2
|
||||
stacked_services: ^1.1.0
|
||||
easy_localization: ^3.0.8
|
||||
omni_datetime_picker: any
|
||||
json_serializable: ^6.8.0
|
||||
waveform_recorder: ^1.8.0
|
||||
|
|
@ -54,7 +56,6 @@ dependencies:
|
|||
flutter_local_notifications: ^20.1.0
|
||||
internet_connection_checker_plus: ^2.9.1+2
|
||||
|
||||
http: any
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
@ -74,6 +75,7 @@ flutter:
|
|||
assets:
|
||||
- assets/icons/
|
||||
- assets/images/
|
||||
- assets/translations/
|
||||
fonts:
|
||||
- family: Aeonik
|
||||
fonts:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import 'package:yimaru_app/services/vimeo_service.dart';
|
|||
import 'package:yimaru_app/services/url_launcher_service.dart';
|
||||
import 'package:yimaru_app/services/phone_caller_service.dart';
|
||||
import 'package:yimaru_app/services/learn_service.dart';
|
||||
import 'package:yimaru_app/services/localization_service.dart';
|
||||
// @stacked-import
|
||||
|
||||
import 'test_helpers.mocks.dart';
|
||||
|
|
@ -56,6 +57,7 @@ import 'test_helpers.mocks.dart';
|
|||
MockSpec<LearnLessonService>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
|
||||
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
|
||||
// @stacked-mock-spec
|
||||
],
|
||||
)
|
||||
|
|
@ -86,6 +88,7 @@ void registerServices() {
|
|||
getAndRegisterLearnLessonService();
|
||||
getAndRegisterLearnService();
|
||||
getAndRegisterLearnService();
|
||||
getAndRegisterLocalizationService();
|
||||
// @stacked-mock-register
|
||||
}
|
||||
|
||||
|
|
@ -275,6 +278,13 @@ MockLearnService getAndRegisterLearnService() {
|
|||
locator.registerSingleton<LearnService>(service);
|
||||
return service;
|
||||
}
|
||||
|
||||
MockLocalizationService getAndRegisterLocalizationService() {
|
||||
_removeRegistrationIfExists<LocalizationService>();
|
||||
final service = MockLocalizationService();
|
||||
locator.registerSingleton<LocalizationService>(service);
|
||||
return service;
|
||||
}
|
||||
// @stacked-mock-create
|
||||
|
||||
void _removeRegistrationIfExists<T extends Object>() {
|
||||
|
|
|
|||
11
test/services/localization_service_test.dart
Normal file
11
test/services/localization_service_test.dart
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:yimaru_app/app/app.locator.dart';
|
||||
|
||||
import '../helpers/test_helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('LocalizationServiceTest -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user