Compare commits

...

41 Commits

Author SHA1 Message Date
4409aef9e2 feat: integration of apple signin 2026-06-02 13:30:38 +03:00
a9389070bf Merge remote-tracking branch 'origin/main' into test-ios 2026-06-01 11:22:07 +03:00
dc0de05f3b Merge branch 'release/0.1.32'
-feat(progress): Integrat my progress section with api.
-fix(localization): Localization state issue fixed.                                                                                                                              -fix(profile_image): Fix profile image downloading issue.
2026-06-01 02:06:29 +03:00
a39396e1d1 -feat(progress): Integrat my progress section with api.
-fix(localization): Localization state issue fixed.
-fix(profile_image): Fix profile image downloading issue.
2026-06-01 02:05:00 +03:00
6ad151c82d Merge tag '0.1.31' into develop
-feat(progress): Integrat my progress section with api.
-fix(localization): Localization state issue fixed.
-fix(profile_image): Fix profile image downloading issue.
2026-06-01 01:39:21 +03:00
723fa38497 Merge branch 'release/0.1.31'
-feat(progress): Integrat my progress section with api.                                                                                                                          -fix(localization): Localization state issue fixed.
-fix(profile_image): Fix profile image downloading issue.
2026-06-01 01:38:11 +03:00
bf34ca9937 -feat(progress): Integrat my progress section with api.
-fix(localization): Localization state issue fixed.
-fix(profile_image): Fix profile image downloading issue.
2026-06-01 01:36:47 +03:00
7f6a5f1d34 -feat(progress): Integrat my progress section with api.
-fix(localization): Localization state issue fixed.
-fix(profile_image): Fix profile image downloading issue.
2026-06-01 01:34:15 +03:00
485aa7a46f Merge remote-tracking branch 'origin/main' into test-ios 2026-05-30 10:57:23 +03:00
a54e734d46 chore: misc changes to ios files 2026-05-30 10:55:59 +03:00
fbedb63e3a Merge tag '0.1.30' into develop
-feat(Payment): Add chapa as payment gateway for learn subscription
2026-05-30 00:04:03 +03:00
2b2d449044 Merge branch 'release/0.1.30'
-feat(Payment): Add chapa as payment gateway for learn subscription
2026-05-30 00:03:25 +03:00
b1e048e637 feat(Payment): Add chapa as payment gateway for learn subscription 2026-05-30 00:02:11 +03:00
dfaa4de44b Merge tag '0.1.29' into develop
-fix(learn): Fix progress tracking issue without payment implementation
2026-05-29 23:14:53 +03:00
5581d00b15 Merge branch 'release/0.1.29'
-fix(learn): Fix progress tracking issue without payment implementation
2026-05-29 23:14:36 +03:00
d4e46d5ddb fix(learn): Fix progress tracking issue without payment implementation 2026-05-29 23:13:12 +03:00
96c7eece9b Merge tag '0.1.28' into develop
-fix(learn): Fix lesson lock logic issue
2026-05-29 16:17:14 +03:00
62c136d8d4 Merge branch 'release/0.1.28'
-fix(learn): Fix lesson lock logic issue
2026-05-29 16:16:44 +03:00
b6872e2a3f fix(learn): Fix lesson lock logic issue 2026-05-29 16:14:31 +03:00
8be9516338 Merge tag '0.1.27' into develop
-feat(learn): Integrate progress tracker
2026-05-28 18:38:28 +03:00
848f0a215e Merge branch 'release/0.1.27'
-feat(learn): Integrate progress tracker
2026-05-28 18:38:00 +03:00
750968d403 feat(learn): Integrate progress tracker 2026-05-28 18:37:11 +03:00
2e23d8808c Merge tag '0.1.26' into develop
-feat(learn): Integrate progress tracker
2026-05-28 17:38:19 +03:00
4b65e389bf Merge branch 'release/0.1.26'
-feat(learn): Integrate progress tracker
2026-05-28 17:37:34 +03:00
80c9d014da feat(learn): Integrate progress tracker 2026-05-28 17:35:43 +03:00
c45f90ad82 Merge tag '0.1.25' into develop
-fix: Update profile detail section
2026-05-27 23:44:11 +03:00
0842a0b357 Merge branch 'release/0.1.25'
-fix: Update profile detail section
2026-05-27 23:43:45 +03:00
9870e738e7 fix: Update profile detail section 2026-05-27 23:39:41 +03:00
974066b8cf fix: Update profile detail section 2026-05-27 23:39:25 +03:00
12f836d427 Merge tag '0.1.24' into develop
-fix: Apply fix for ArifPay`
2026-05-27 16:07:14 +03:00
2024dd3b6d Merge branch 'release/0.1.24'
-fix: Apply fix for ArifPay
2026-05-27 16:06:40 +03:00
54356812d6 fix: Apply fix for ArifPay 2026-05-27 16:05:42 +03:00
7a54d0c4c8 fix: Apply fix for ArifPay 2026-05-27 16:05:21 +03:00
59ffca2155 Merge tag '0.1.23' into develop
-fix: Apply UAT comments
2026-05-26 16:57:47 +03:00
45602d0da7 Merge branch 'release/0.1.23'
-fix: Apply UAT comments
2026-05-26 16:57:29 +03:00
25ca644626 fix: Apply UAT comments 2026-05-26 16:56:44 +03:00
3b76e5fafa fix: Apply UAT comments 2026-05-26 16:56:16 +03:00
a67a2ec7ea Merge tag '0.1.22' into develop
-fix: Apply fix on onboarding issue
2026-05-26 16:17:42 +03:00
f71220fa80 Merge branch 'release/0.1.22'
-fix: Apply fix on onboarding issue
2026-05-26 16:17:19 +03:00
f957d36e14 fix: Apply fix on onboarding issue 2026-05-26 16:16:12 +03:00
4cf063dce0 Merge tag '0.1.21' into develop
-fix: Applying play console comments
2026-05-25 14:15:26 +03:00
162 changed files with 4616 additions and 2565 deletions

View File

@ -2,19 +2,21 @@
"loading": "በመጫን ላይ", "loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ", "welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ", "dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል", "email": "ኢሜይል",
"password": "የይለፍ ቃል", "password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?", "forgot_password": "የይለፍ ቃል ረሱ?",
"cont": "ቀጥል", "cont": "ቀጥል",
"register": "ይመዝገቡ", "register": "ይመዝገቡ",
"login_with_google": "በጉግል ይግቡ", "login_with_google": "በጉግል ይግቡ",
"login_with_apple": "በአፕል ይግቡ",
"or": "ወይም", "or": "ወይም",
"login_with_phone": "በስልክ ቁጥር ይግቡ", "login_with_phone": "በስልክ ቁጥር ይግቡ",
"create_account": "አዲስ መለያ ይፍጠሩ", "create_account": "አዲስ መለያ ይፍጠሩ",
"already_have_account": "መለያ አለዎት?", "already_have_account": "መለያ አለዎት?",
"login": " ይግቡ ", "login": " ይግቡ ",
"register_with_google": "በጉግል ይመዝገቡ", "register_with_google": "በጉግል ይመዝገቡ",
"register_with_apple": "በአፕል ይመዝገቡ",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ", "register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።", "enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"login_with_email": "በኢሜይል ይግቡ", "login_with_email": "በኢሜይል ይግቡ",
@ -134,7 +136,7 @@
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር", "begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው", "speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ", "view_results": "ውጤቶቼን እይ",
@ -153,20 +155,47 @@
"what_should_we_call_you": "ምን ብለን እንጠራህ?", "what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ", "choose_your_gender": "ጾታህን ምረጥ",
"gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።" "gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?",
"age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?",
"education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።",
"your_occupation": "ስራህ ምንድን ነው?",
"occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"location": "ከየት ነህ?",
"select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ",
"select_country": "አገር ምረጥ",
"learning_goal": "የመማር ዓላማህን ምረጥ",
"language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?",
"your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።",
"write_your_goal": "ዓላማህን ጻፍ…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"your_interest": "ፍላጎትህን ጻፍ…",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
"skip": "ዝለል",
"finish_level": "ደረጃውን አጠናቅቅ",
"likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ",
"great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።",
"lets_start_practice": "ልምምድህን እንጀምር",
"welcome_abroad": "እንኳን ደህና መጣህ",
"ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።",
"finish": "አጠናቅቅ",
"finish_all_practice_lesson": "ይህን ልምምድ ለመውሰድ የቀድሞውን የትምህርት ልምምድ ያጠናቅቁ",
"finish_all_practice_module": "የሞጁሉን ልምምድ ለመውሰድ የትምህርት ልምምዶችን ያጠናቅቁ",
"finish_all_practice_course": "የኮርሱን ልምምድ ለመውሰድ የሞጁል ልምምዶችን ያጠናቅቁ",
"finish_all_practice_previouse_module": "ይህን ልምምድ ለመውሰድ የቀድሞውን የሞጁል ልምምድ ያጠናቅቁ",
"finish_all_practice_previouse_course": "ይህን ለመውሰድ የቀድሞውን የኮርስ ልምምድ ያጠናቅቁ",
"track_journey": "የትምህርት ጉዞዎን ይከታተሉ እና በጊዜ ሂደት ያሳዩትን እድገት ይመልከቱ።",
"learn_english": "እንግሊዝኛ ይማሩ",
"keep_momentum":"በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።",
"completed_practices": "የተጠናቀቁ ልምምዶች",
"total_practices": "ጠቅላላ ልምምዶች",
"progress_percentage": "የእድገት መቶኛ"
} }

View File

@ -1,27 +1,29 @@
{
{"loading": "Loading", "loading": "Loading",
"welcome_back": "Welcome back", "welcome_back": "Welcome back",
"checking_user_info": "Checking user info", "checking_user_info": "Checking user info",
"dont_have_account": "Don't have an account? Register", "dont_have_account": "Don't have an account?",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
"forgot_password": "Forgot password?", "forgot_password": "Forgot password?",
"cont": "Continue", "cont": "Continue",
"register": "Register", "register": "Register",
"login_with_google": "Login with Google", "login_with_google": "Login with Google",
"login_with_apple": "Login with Apple",
"or": "Or", "or": "Or",
"login_with_phone": "Login with phone number", "login_with_phone": "Login with phone number",
"create_account": "Create an account", "create_account": "Create an account",
"already_have_account": "Already have an account?", "already_have_account": "Already have an account?",
"login": "Login", "login": "Login",
"register_with_google": "Register with Google", "register_with_google": "Register with Google",
"register_with_apple": "Register with Apple",
"register_with_phone": "Register with phone number", "register_with_phone": "Register with phone number",
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.", "enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
"login_with_email": "Login with email", "login_with_email": "Login with email",
"create_password": "Create password", "create_password": "Create password",
"confirm_password": "Confirm password", "confirm_password": "Confirm password",
"eight_character_minimum": "8 characters minimum", "eight_character_minimum": "8 characters minimum",
"password_math": "password match", "password_match": "password match",
"sign_up_agreement": "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy", "sign_up_agreement": "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"terms_of_services": "Terms of Service", "terms_of_services": "Terms of Service",
"and": "and", "and": "and",
@ -125,7 +127,7 @@
"open_in_telegram": "Open in Telegram", "open_in_telegram": "Open in Telegram",
"search_for": "Search for", "search_for": "Search for",
"current_level": "Current Level", "current_level": "Current Level",
"keep_up_the_great_work": "Keep up the great work! You\\'re doing amazing.", "keep_up_the_great_work": "Keep up the great work! You're doing amazing.",
"no_practice_available": "No practice available!", "no_practice_available": "No practice available!",
"begin_module_practice": "Begin Module Practice", "begin_module_practice": "Begin Module Practice",
"lets_practice_lesson": "Lets Practice", "lets_practice_lesson": "Lets Practice",
@ -153,7 +155,45 @@
"what_should_we_call_you": "What should we call you?", "what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.", "name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?", "choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender." "gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish",
"finish_all_practice_lesson": "Finish the previous lesson practice to take this practice",
"finish_all_practice_module": "Finish the lesson practices to take the Module Practice",
"finish_all_practice_course": "Finish the Module practices to take the Course practice",
"finish_all_practice_previouse_module": "Finish the previous Module practice to take this practice",
"finish_all_practice_previouse_course": "Finish the previous course practice to take this",
"track_journey": "Track your learning journey and see your growth over time.",
"learn_english": "Learn English",
"keep_momentum": "Great job! Keep the momentum.",
"completed_practices": "Completed Practices",
"total_practices": "Total Practices",
"progress_percentage": "Progress Percentage"
} }

View File

@ -20,7 +20,5 @@
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

46
ios/Podfile Normal file
View File

@ -0,0 +1,46 @@
# Uncomment this line to define a global platform for your project
platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end

28
ios/Podfile.lock Normal file
View File

@ -0,0 +1,28 @@
PODS:
- Flutter (1.0.0)
- flutter_phone_direct_caller (0.0.1):
- Flutter
- permission_handler_apple (9.3.0):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_phone_direct_caller (from `.symlinks/plugins/flutter_phone_direct_caller/ios`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_phone_direct_caller:
:path: ".symlinks/plugins/flutter_phone_direct_caller/ios"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_phone_direct_caller: 7d5d72794577b96f12b4b6da13a9ef90ba438665
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
PODFILE CHECKSUM: 4b015915ec662986b54bf30ab778da63f7dda016
COCOAPODS: 1.16.2

View File

@ -11,9 +11,13 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
896DC9E666DF8098D827C010 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9793345F00B89E38C23EBB8 /* Pods_RunnerTests.framework */; };
8E1B3E4A2C540A0B00F51C11 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8E1B3E492C540A0B00F51C11 /* GoogleService-Info.plist */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
99CE3BFD23F69C6D49568DE0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B517F10FA92BB14B3CDC5A /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -42,12 +46,19 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
18336A33563E3B5B5B9974CC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
20E44080F42EAC6B045A6D89 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
69301B8842E33A5CD16999A8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
74C1B93B8C9E9562FD058B56 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8E1B3E492C540A0B00F51C11 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
93B517F10FA92BB14B3CDC5A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -55,13 +66,27 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
ACEEA7C32CFC6E9900D60211 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
A8C2A6C7D1D99F7BA12EAF94 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
F1F6AAAC52D909E27AEDEFC0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
F9793345F00B89E38C23EBB8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
932D3841F05A890DB5B188A4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
896DC9E666DF8098D827C010 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = { 97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
99CE3BFD23F69C6D49568DE0 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -76,9 +101,32 @@
path = RunnerTests; path = RunnerTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
633D6DCBF46C9B68DCD511FE /* Frameworks */ = {
isa = PBXGroup;
children = (
93B517F10FA92BB14B3CDC5A /* Pods_Runner.framework */,
F9793345F00B89E38C23EBB8 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
8096B886696A019BCD318B6B /* Pods */ = {
isa = PBXGroup;
children = (
74C1B93B8C9E9562FD058B56 /* Pods-Runner.debug.xcconfig */,
69301B8842E33A5CD16999A8 /* Pods-Runner.release.xcconfig */,
F1F6AAAC52D909E27AEDEFC0 /* Pods-Runner.profile.xcconfig */,
20E44080F42EAC6B045A6D89 /* Pods-RunnerTests.debug.xcconfig */,
18336A33563E3B5B5B9974CC /* Pods-RunnerTests.release.xcconfig */,
A8C2A6C7D1D99F7BA12EAF94 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@ -94,6 +142,8 @@
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */, 331C8082294A63A400263BE5 /* RunnerTests */,
8096B886696A019BCD318B6B /* Pods */,
633D6DCBF46C9B68DCD511FE /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -113,6 +163,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */, 97C147021CF9000F007C117D /* Info.plist */,
8E1B3E492C540A0B00F51C11 /* GoogleService-Info.plist */,
ACEEA7C32CFC6E9900D60211 /* Runner.entitlements */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@ -128,8 +180,10 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
94F71B9A2AE7A340918A2B71 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */, 331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */, 331C807F294A63A400263BE5 /* Resources */,
932D3841F05A890DB5B188A4 /* Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -145,18 +199,24 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
EDCDA15227D1D12483183F4E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
DE13979F911D29D4A95BCE2F /* [CP] Embed Pods Frameworks */,
E684A37538F596FB5432DE3F /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
); );
name = Runner; name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner; productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -178,6 +238,11 @@
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100; LastSwiftMigration = 1100;
SystemCapabilities = {
com.apple.SignInWithApple = {
enabled = 1;
};
};
}; };
}; };
}; };
@ -190,6 +255,9 @@
Base, Base,
); );
mainGroup = 97C146E51CF9000F007C117D; mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -215,6 +283,7 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
8E1B3E4A2C540A0B00F51C11 /* GoogleService-Info.plist in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -238,6 +307,28 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
94F71B9A2AE7A340918A2B71 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@ -253,6 +344,62 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
DE13979F911D29D4A95BCE2F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E684A37538F596FB5432DE3F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
EDCDA15227D1D12483183F4E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -346,7 +493,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -361,15 +508,23 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Yimaru LMS TestApp App Store Profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -378,13 +533,14 @@
}; };
331C8088294A63A400263BE5 /* Debug */ = { 331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 20E44080F42EAC6B045A6D89 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -395,13 +551,14 @@
}; };
331C8089294A63A400263BE5 /* Release */ = { 331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 18336A33563E3B5B5B9974CC /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -410,13 +567,14 @@
}; };
331C808A294A63A400263BE5 /* Profile */ = { 331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = A8C2A6C7D1D99F7BA12EAF94 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -472,7 +630,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -523,7 +681,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -540,15 +698,23 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Yimaru LMS TestApp App Store Profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -562,15 +728,23 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app; PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Yimaru LMS TestApp App Store Profile";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
@ -611,6 +785,20 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 97C146E61CF9000F007C117D /* Project object */; rootObject = 97C146E61CF9000F007C117D /* Project object */;
} }

View File

@ -0,0 +1,167 @@
{
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "appauth-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/openid/AppAuth-iOS.git",
"state" : {
"revision" : "145104f5ea9d58ae21b60add007c33c1cc0c948e",
"version" : "2.0.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "8d5b4189f1f482df8d5c58c9985ea70491ef5382",
"version" : "12.14.0"
}
},
{
"identity" : "flutterfire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/flutterfire",
"state" : {
"revision" : "05731e3fb091093546db363e379bff166f7286a3",
"version" : "4.4.0-firebase-core-swift"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "9bfcc6cf435b2e7c5562c1900b8680c594fa9a64",
"version" : "3.6.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "219e564a8510e983e675c94f77f7f7c50049f22d",
"version" : "12.14.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googlesignin-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleSignIn-iOS.git",
"state" : {
"revision" : "913b4005ea26aebe1c97d54e35ad82a515924c71",
"version" : "9.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "gtmappauth",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GTMAppAuth.git",
"state" : {
"revision" : "56e0ccf09a6dd29dc7e68bdf729598240ca8aa16",
"version" : "5.0.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "fea17c02d767f46b23070fdfdacc28a03a39232a",
"version" : "1.5.1"
}
}
],
"version" : 2
}

View File

@ -5,6 +5,24 @@
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"

View File

@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@ -0,0 +1,167 @@
{
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "appauth-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/openid/AppAuth-iOS.git",
"state" : {
"revision" : "145104f5ea9d58ae21b60add007c33c1cc0c948e",
"version" : "2.0.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "8d5b4189f1f482df8d5c58c9985ea70491ef5382",
"version" : "12.14.0"
}
},
{
"identity" : "flutterfire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/flutterfire",
"state" : {
"revision" : "05731e3fb091093546db363e379bff166f7286a3",
"version" : "4.4.0-firebase-core-swift"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "9bfcc6cf435b2e7c5562c1900b8680c594fa9a64",
"version" : "3.6.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "219e564a8510e983e675c94f77f7f7c50049f22d",
"version" : "12.14.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googlesignin-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleSignIn-iOS.git",
"state" : {
"revision" : "913b4005ea26aebe1c97d54e35ad82a515924c71",
"version" : "9.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "gtmappauth",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GTMAppAuth.git",
"state" : {
"revision" : "56e0ccf09a6dd29dc7e68bdf729598240ca8aa16",
"version" : "5.0.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "fea17c02d767f46b23070fdfdacc28a03a39232a",
"version" : "1.5.1"
}
}
],
"version" : 2
}

View File

@ -2,12 +2,15 @@ import Flutter
import UIKit import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} }

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>900714037062-3qqf7urii8vg99id91nmmuvom3fm5c1u.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.900714037062-3qqf7urii8vg99id91nmmuvom3fm5c1u</string>
<key>ANDROID_CLIENT_ID</key>
<string>900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk</string>
<key>GCM_SENDER_ID</key>
<string>900714037062</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.yimaru.lms.testapp</string>
<key>PROJECT_ID</key>
<string>yimaru-academy-5e7e2</string>
<key>STORAGE_BUCKET</key>
<string>yimaru-academy-5e7e2.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:900714037062:ios:45b484d79222c3ab4e6f47</string>
</dict>
</plist>

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -16,6 +18,17 @@
<string>yimaru_app</string> <string>yimaru_app</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.900714037062-3qqf7urii8vg99id91nmmuvom3fm5c1u</string>
</array>
</dict>
</array>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
@ -24,10 +37,35 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
@ -41,11 +79,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIStatusBarHidden</key>
<false/>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>

View File

@ -44,19 +44,21 @@ import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart'; import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/services/in_app_update_service.dart'; import 'package:yimaru_app/services/in_app_update_service.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
import 'package:yimaru_app/services/vimeo_service.dart'; import 'package:yimaru_app/services/vimeo_service.dart';
import 'package:yimaru_app/services/url_launcher_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/phone_caller_service.dart';
import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart'; import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart';
import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart';
import 'package:yimaru_app/services/learn_service.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_catalog/course_catalog_view.dart';
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart'; import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart';
import 'package:yimaru_app/services/localization_service.dart'; import 'package:yimaru_app/services/localization_service.dart';
import 'package:yimaru_app/ui/views/landing/landing_view.dart'; import 'package:yimaru_app/ui/views/landing/landing_view.dart';
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
import 'package:yimaru_app/services/onboarding_service.dart';
import 'package:yimaru_app/services/apple_auth_service.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/payment/payment_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -88,15 +90,14 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
MaterialRoute(page: DuolingoView), MaterialRoute(page: DuolingoView),
MaterialRoute(page: CourseView), MaterialRoute(page: CourseView),
MaterialRoute(page: LearnProgramView), MaterialRoute(page: LearnProgramView),
MaterialRoute(page: LearnCourseView),
MaterialRoute(page: AssessmentView), MaterialRoute(page: AssessmentView),
MaterialRoute(page: LearnSubscriptionView), MaterialRoute(page: LearnSubscriptionView),
MaterialRoute(page: ArifPayView),
MaterialRoute(page: CourseCatalogView), MaterialRoute(page: CourseCatalogView),
MaterialRoute(page: CourseUnitView), MaterialRoute(page: CourseUnitView),
MaterialRoute(page: LandingView), MaterialRoute(page: LandingView),
MaterialRoute(page: CourseModuleView), MaterialRoute(page: CourseModuleView),
MaterialRoute(page: LearnCourseView), MaterialRoute(page: LearnCourseView),
MaterialRoute(page: PaymentView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [
@ -123,6 +124,8 @@ import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
LazySingleton(classType: PhoneCallerService), LazySingleton(classType: PhoneCallerService),
LazySingleton(classType: LearnService), LazySingleton(classType: LearnService),
LazySingleton(classType: LocalizationService), LazySingleton(classType: LocalizationService),
LazySingleton(classType: OnboardingService),
LazySingleton(classType: AppleAuthService),
// @stacked-service // @stacked-service
], ],
bottomsheets: [ bottomsheets: [

View File

@ -13,6 +13,7 @@ import 'package:stacked_services/src/navigation/navigation_service.dart';
import 'package:stacked_shared/stacked_shared.dart'; import 'package:stacked_shared/stacked_shared.dart';
import '../services/api_service.dart'; import '../services/api_service.dart';
import '../services/apple_auth_service.dart';
import '../services/audio_player_service.dart'; import '../services/audio_player_service.dart';
import '../services/authentication_service.dart'; import '../services/authentication_service.dart';
import '../services/course_service.dart'; import '../services/course_service.dart';
@ -24,6 +25,7 @@ import '../services/in_app_update_service.dart';
import '../services/learn_service.dart'; import '../services/learn_service.dart';
import '../services/localization_service.dart'; import '../services/localization_service.dart';
import '../services/notification_service.dart'; import '../services/notification_service.dart';
import '../services/onboarding_service.dart';
import '../services/permission_handler_service.dart'; import '../services/permission_handler_service.dart';
import '../services/phone_caller_service.dart'; import '../services/phone_caller_service.dart';
import '../services/secure_storage_service.dart'; import '../services/secure_storage_service.dart';
@ -65,4 +67,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => PhoneCallerService()); locator.registerLazySingleton(() => PhoneCallerService());
locator.registerLazySingleton(() => LearnService()); locator.registerLazySingleton(() => LearnService());
locator.registerLazySingleton(() => LocalizationService()); locator.registerLazySingleton(() => LocalizationService());
locator.registerLazySingleton(() => OnboardingService());
locator.registerLazySingleton(() => AppleAuthService());
} }

View File

@ -6,10 +6,10 @@
// ************************************************************************** // **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:flutter/material.dart' as _i37;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as _i37;
import 'package:stacked/stacked.dart' as _i1; 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 _i42; import 'package:yimaru_app/models/course.dart' as _i42;
import 'package:yimaru_app/models/course_catalog.dart' as _i44; import 'package:yimaru_app/models/course_catalog.dart' as _i44;
import 'package:yimaru_app/models/course_lesson.dart' as _i43; import 'package:yimaru_app/models/course_lesson.dart' as _i43;
@ -17,33 +17,33 @@ import 'package:yimaru_app/models/course_module.dart' as _i45;
import 'package:yimaru_app/models/learn_course.dart' as _i38; import 'package:yimaru_app/models/learn_course.dart' as _i38;
import 'package:yimaru_app/models/learn_lesson.dart' as _i40; import 'package:yimaru_app/models/learn_lesson.dart' as _i40;
import 'package:yimaru_app/models/learn_module.dart' as _i39; import 'package:yimaru_app/models/learn_module.dart' as _i39;
import 'package:yimaru_app/models/learn_subscription.dart' as _i46;
import 'package:yimaru_app/ui/common/enmus.dart' as _i41; import 'package:yimaru_app/ui/common/enmus.dart' as _i41;
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
as _i9; as _i9;
import 'package:yimaru_app/ui/views/arif_pay/arif_pay_view.dart' as _i32; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i29;
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i30;
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
as _i12; as _i12;
import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27; import 'package:yimaru_app/ui/views/course/course_view.dart' as _i27;
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart' import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart'
as _i33; as _i31;
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart' import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart'
as _i25; as _i25;
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart' import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'
as _i36; as _i34;
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart' import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart'
as _i23; as _i23;
import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i34; import 'package:yimaru_app/ui/views/course_unit/course_unit_view.dart' as _i32;
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i26; import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart' as _i26;
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i24; import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i24;
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart' import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'
as _i20; as _i20;
import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2;
import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i35; import 'package:yimaru_app/ui/views/landing/landing_view.dart' as _i33;
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i13;
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart' import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart'
as _i29; as _i35;
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'
as _i19; as _i19;
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart'
@ -55,9 +55,10 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart' import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'
as _i28; as _i28;
import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart' import 'package:yimaru_app/ui/views/learn_subscription/learn_subscription_view.dart'
as _i31; as _i30;
import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i17;
import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3;
import 'package:yimaru_app/ui/views/payment/payment_view.dart' as _i36;
import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart' import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart'
as _i14; as _i14;
import 'package:yimaru_app/ui/views/profile/profile_view.dart' as _i5; import 'package:yimaru_app/ui/views/profile/profile_view.dart' as _i5;
@ -127,14 +128,10 @@ class Routes {
static const learnProgramView = '/learn-program-view'; static const learnProgramView = '/learn-program-view';
static const learnCourseView = '/learn-course-view';
static const assessmentView = '/assessment-view'; static const assessmentView = '/assessment-view';
static const learnSubscriptionView = '/learn-subscription-view'; static const learnSubscriptionView = '/learn-subscription-view';
static const arifPayView = '/arif-pay-view';
static const courseCatalogView = '/course-catalog-view'; static const courseCatalogView = '/course-catalog-view';
static const courseUnitView = '/course-unit-view'; static const courseUnitView = '/course-unit-view';
@ -143,6 +140,9 @@ class Routes {
static const courseModuleView = '/course-module-view'; static const courseModuleView = '/course-module-view';
static const learnCourseView = '/learn-course-view';
static const paymentView = '/payment-view';
static const all = <String>{ static const all = <String>{
homeView, homeView,
@ -172,14 +172,14 @@ class Routes {
duolingoView, duolingoView,
courseView, courseView,
learnProgramView, learnProgramView,
learnCourseView,
assessmentView, assessmentView,
learnSubscriptionView, learnSubscriptionView,
arifPayView,
courseCatalogView, courseCatalogView,
courseUnitView, courseUnitView,
landingView, landingView,
courseModuleView, courseModuleView,
learnCourseView,
paymentView,
}; };
} }
@ -293,41 +293,37 @@ class StackedRouter extends _i1.RouterBase {
Routes.learnProgramView, Routes.learnProgramView,
page: _i28.LearnProgramView, page: _i28.LearnProgramView,
), ),
_i1.RouteDef(
Routes.learnCourseView,
page: _i29.LearnCourseView,
),
_i1.RouteDef( _i1.RouteDef(
Routes.assessmentView, Routes.assessmentView,
page: _i30.AssessmentView, page: _i29.AssessmentView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnSubscriptionView, Routes.learnSubscriptionView,
page: _i31.LearnSubscriptionView, page: _i30.LearnSubscriptionView,
),
_i1.RouteDef(
Routes.arifPayView,
page: _i32.ArifPayView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseCatalogView, Routes.courseCatalogView,
page: _i33.CourseCatalogView, page: _i31.CourseCatalogView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseUnitView, Routes.courseUnitView,
page: _i34.CourseUnitView, page: _i32.CourseUnitView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.landingView, Routes.landingView,
page: _i35.LandingView, page: _i33.LandingView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.courseModuleView, Routes.courseModuleView,
page: _i36.CourseModuleView, page: _i34.CourseModuleView,
), ),
_i1.RouteDef( _i1.RouteDef(
Routes.learnCourseView, Routes.learnCourseView,
page: _i29.LearnCourseView, page: _i35.LearnCourseView,
),
_i1.RouteDef(
Routes.paymentView,
page: _i36.PaymentView,
), ),
]; ];
@ -506,6 +502,7 @@ class StackedRouter extends _i1.RouterBase {
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i21.LearnLessonDetailView( builder: (context) => _i21.LearnLessonDetailView(
key: args.key, key: args.key,
index: args.index,
lesson: args.lesson, lesson: args.lesson,
module: args.module, module: args.module,
hasPractice: args.hasPractice), hasPractice: args.hasPractice),
@ -577,72 +574,72 @@ class StackedRouter extends _i1.RouterBase {
settings: data, settings: data,
); );
}, },
_i29.LearnCourseView: (data) { _i29.AssessmentView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i29.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
_i30.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false); final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i30.AssessmentView(key: args.key, data: args.data), _i29.AssessmentView(key: args.key, data: args.data),
settings: data, settings: data,
); );
}, },
_i31.LearnSubscriptionView: (data) { _i30.LearnSubscriptionView: (data) {
final args = data.getArgs<LearnSubscriptionViewArguments>( final args = data.getArgs<LearnSubscriptionViewArguments>(
orElse: () => const LearnSubscriptionViewArguments(), orElse: () => const LearnSubscriptionViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i31.LearnSubscriptionView(key: args.key), builder: (context) => _i30.LearnSubscriptionView(key: args.key),
settings: data, settings: data,
); );
}, },
_i32.ArifPayView: (data) { _i31.CourseCatalogView: (data) {
final args = data.getArgs<ArifPayViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i32.ArifPayView(key: args.key, phone: args.phone),
settings: data,
);
},
_i33.CourseCatalogView: (data) {
final args = data.getArgs<CourseCatalogViewArguments>( final args = data.getArgs<CourseCatalogViewArguments>(
orElse: () => const CourseCatalogViewArguments(), orElse: () => const CourseCatalogViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i33.CourseCatalogView(key: args.key), builder: (context) => _i31.CourseCatalogView(key: args.key),
settings: data, settings: data,
); );
}, },
_i34.CourseUnitView: (data) { _i32.CourseUnitView: (data) {
final args = data.getArgs<CourseUnitViewArguments>(nullOk: false); final args = data.getArgs<CourseUnitViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i34.CourseUnitView(key: args.key, catalog: args.catalog), _i32.CourseUnitView(key: args.key, catalog: args.catalog),
settings: data, settings: data,
); );
}, },
_i35.LandingView: (data) { _i33.LandingView: (data) {
final args = data.getArgs<LandingViewArguments>( final args = data.getArgs<LandingViewArguments>(
orElse: () => const LandingViewArguments(), orElse: () => const LandingViewArguments(),
); );
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i35.LandingView(key: args.key), builder: (context) => _i33.LandingView(key: args.key),
settings: data, settings: data,
); );
}, },
_i36.CourseModuleView: (data) { _i34.CourseModuleView: (data) {
final args = data.getArgs<CourseModuleViewArguments>(nullOk: false); final args = data.getArgs<CourseModuleViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>( return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i36.CourseModuleView( builder: (context) => _i34.CourseModuleView(
key: args.key, module: args.module, catalog: args.catalog), key: args.key, module: args.module, catalog: args.catalog),
settings: data, settings: data,
); );
}, },
_i35.LearnCourseView: (data) {
final args = data.getArgs<LearnCourseViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i35.LearnCourseView(key: args.key, id: args.id),
settings: data,
);
},
_i36.PaymentView: (data) {
final args = data.getArgs<PaymentViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i36.PaymentView(
key: args.key, phone: args.phone, subscription: args.subscription),
settings: data,
);
},
}; };
@override @override
@ -1088,6 +1085,7 @@ class ForgetPasswordViewArguments {
class LearnLessonDetailViewArguments { class LearnLessonDetailViewArguments {
const LearnLessonDetailViewArguments({ const LearnLessonDetailViewArguments({
this.key, this.key,
required this.index,
required this.lesson, required this.lesson,
required this.module, required this.module,
required this.hasPractice, required this.hasPractice,
@ -1095,6 +1093,8 @@ class LearnLessonDetailViewArguments {
final _i37.Key? key; final _i37.Key? key;
final int index;
final _i40.LearnLesson lesson; final _i40.LearnLesson lesson;
final _i39.LearnModule module; final _i39.LearnModule module;
@ -1103,13 +1103,14 @@ class LearnLessonDetailViewArguments {
@override @override
String toString() { String toString() {
return '{"key": "$key", "lesson": "$lesson", "module": "$module", "hasPractice": "$hasPractice"}'; return '{"key": "$key", "index": "$index", "lesson": "$lesson", "module": "$module", "hasPractice": "$hasPractice"}';
} }
@override @override
bool operator ==(covariant LearnLessonDetailViewArguments other) { bool operator ==(covariant LearnLessonDetailViewArguments other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.key == key && return other.key == key &&
other.index == index &&
other.lesson == lesson && other.lesson == lesson &&
other.module == module && other.module == module &&
other.hasPractice == hasPractice; other.hasPractice == hasPractice;
@ -1118,6 +1119,7 @@ class LearnLessonDetailViewArguments {
@override @override
int get hashCode { int get hashCode {
return key.hashCode ^ return key.hashCode ^
index.hashCode ^
lesson.hashCode ^ lesson.hashCode ^
module.hashCode ^ module.hashCode ^
hasPractice.hashCode; hasPractice.hashCode;
@ -1328,33 +1330,6 @@ class LearnProgramViewArguments {
} }
} }
class LearnCourseViewArguments {
const LearnCourseViewArguments({
this.key,
required this.id,
});
final _i37.Key? key;
final int id;
@override
String toString() {
return '{"key": "$key", "id": "$id"}';
}
@override
bool operator ==(covariant LearnCourseViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.id == id;
}
@override
int get hashCode {
return key.hashCode ^ id.hashCode;
}
}
class AssessmentViewArguments { class AssessmentViewArguments {
const AssessmentViewArguments({ const AssessmentViewArguments({
this.key, this.key,
@ -1404,33 +1379,6 @@ class LearnSubscriptionViewArguments {
} }
} }
class ArifPayViewArguments {
const ArifPayViewArguments({
this.key,
required this.phone,
});
final _i37.Key? key;
final String phone;
@override
String toString() {
return '{"key": "$key", "phone": "$phone"}';
}
@override
bool operator ==(covariant ArifPayViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.phone == phone;
}
@override
int get hashCode {
return key.hashCode ^ phone.hashCode;
}
}
class CourseCatalogViewArguments { class CourseCatalogViewArguments {
const CourseCatalogViewArguments({this.key}); const CourseCatalogViewArguments({this.key});
@ -1534,7 +1482,66 @@ class CourseModuleViewArguments {
} }
} }
extension NavigatorStateExtension on _i46.NavigationService { class LearnCourseViewArguments {
const LearnCourseViewArguments({
this.key,
required this.id,
});
final _i37.Key? key;
final int id;
@override
String toString() {
return '{"key": "$key", "id": "$id"}';
}
@override
bool operator ==(covariant LearnCourseViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.id == id;
}
@override
int get hashCode {
return key.hashCode ^ id.hashCode;
}
}
class PaymentViewArguments {
const PaymentViewArguments({
this.key,
required this.phone,
required this.subscription,
});
final _i37.Key? key;
final String phone;
final _i46.LearnSubscription subscription;
@override
String toString() {
return '{"key": "$key", "phone": "$phone", "subscription": "$subscription"}';
}
@override
bool operator ==(covariant PaymentViewArguments other) {
if (identical(this, other)) return true;
return other.key == key &&
other.phone == phone &&
other.subscription == subscription;
}
@override
int get hashCode {
return key.hashCode ^ phone.hashCode ^ subscription.hashCode;
}
}
extension NavigatorStateExtension on _i47.NavigationService {
Future<dynamic> navigateToHomeView({ Future<dynamic> navigateToHomeView({
_i37.Key? key, _i37.Key? key,
int? routerId, int? routerId,
@ -1844,6 +1851,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnLessonDetailView({ Future<dynamic> navigateToLearnLessonDetailView({
_i37.Key? key, _i37.Key? key,
required int index,
required _i40.LearnLesson lesson, required _i40.LearnLesson lesson,
required _i39.LearnModule module, required _i39.LearnModule module,
required bool hasPractice, required bool hasPractice,
@ -1855,7 +1863,11 @@ extension NavigatorStateExtension on _i46.NavigationService {
}) async { }) async {
return navigateTo<dynamic>(Routes.learnLessonDetailView, return navigateTo<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments( arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, module: module, hasPractice: hasPractice), key: key,
index: index,
lesson: lesson,
module: module,
hasPractice: hasPractice),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -1991,23 +2003,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToAssessmentView({ Future<dynamic> navigateToAssessmentView({
_i37.Key? key, _i37.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
@ -2041,23 +2036,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToArifPayView({
_i37.Key? key,
required String phone,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.arifPayView,
arguments: ArifPayViewArguments(key: key, phone: phone),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToCourseCatalogView({ Future<dynamic> navigateToCourseCatalogView({
_i37.Key? key, _i37.Key? key,
int? routerId, int? routerId,
@ -2126,7 +2104,41 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> navigateToPaymentView({
_i37.Key? key,
required String phone,
required _i46.LearnSubscription subscription,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.paymentView,
arguments: PaymentViewArguments(
key: key, phone: phone, subscription: subscription),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView({ Future<dynamic> replaceWithHomeView({
_i37.Key? key, _i37.Key? key,
@ -2437,6 +2449,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnLessonDetailView({ Future<dynamic> replaceWithLearnLessonDetailView({
_i37.Key? key, _i37.Key? key,
required int index,
required _i40.LearnLesson lesson, required _i40.LearnLesson lesson,
required _i39.LearnModule module, required _i39.LearnModule module,
required bool hasPractice, required bool hasPractice,
@ -2448,7 +2461,11 @@ extension NavigatorStateExtension on _i46.NavigationService {
}) async { }) async {
return replaceWith<dynamic>(Routes.learnLessonDetailView, return replaceWith<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments( arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, module: module, hasPractice: hasPractice), key: key,
index: index,
lesson: lesson,
module: module,
hasPractice: hasPractice),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -2584,23 +2601,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithAssessmentView({ Future<dynamic> replaceWithAssessmentView({
_i37.Key? key, _i37.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
@ -2634,23 +2634,6 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithArifPayView({
_i37.Key? key,
required String phone,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.arifPayView,
arguments: ArifPayViewArguments(key: key, phone: phone),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithCourseCatalogView({ Future<dynamic> replaceWithCourseCatalogView({
_i37.Key? key, _i37.Key? key,
int? routerId, int? routerId,
@ -2719,5 +2702,39 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithLearnCourseView({
_i37.Key? key,
required int id,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.learnCourseView,
arguments: LearnCourseViewArguments(key: key, id: id),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithPaymentView({
_i37.Key? key,
required String phone,
required _i46.LearnSubscription subscription,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.paymentView,
arguments: PaymentViewArguments(
key: key, phone: phone, subscription: subscription),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
} }

View File

@ -59,14 +59,14 @@ class DefaultFirebaseOptions {
static const FirebaseOptions ios = FirebaseOptions( static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk', apiKey: 'AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk',
appId: '1:900714037062:ios:1caf8f24a4333b8e4e6f47', appId: '1:900714037062:ios:45b484d79222c3ab4e6f47',
messagingSenderId: '900714037062', messagingSenderId: '900714037062',
projectId: 'yimaru-academy-5e7e2', projectId: 'yimaru-academy-5e7e2',
storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app', storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app',
androidClientId: androidClientId:
'900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com', '900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com',
iosClientId: iosClientId:
'900714037062-35bg0hsou56hg37mbcbpiar9uti7tcku.apps.googleusercontent.com', '900714037062-3qqf7urii8vg99id91nmmuvom3fm5c1u.apps.googleusercontent.com',
iosBundleId: 'com.yimaru.lms.app', iosBundleId: 'com.yimaru.lms.testapp',
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:toastification/toastification.dart'; import 'package:toastification/toastification.dart';
import 'package:yimaru_app/app/app.bottomsheets.dart'; import 'package:yimaru_app/app/app.bottomsheets.dart';
import 'package:yimaru_app/app/app.dialogs.dart'; import 'package:yimaru_app/app/app.dialogs.dart';
@ -13,17 +14,22 @@ import 'firebase_options.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await setupLocator(); await setupLocator();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await locator<NotificationService>().initialize(); await locator<NotificationService>().initialize();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
setupDialogUi(); setupDialogUi();
setupBottomSheetUi(); setupBottomSheetUi();
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp( runApp(
EasyLocalization( EasyLocalization(
supportedLocales: const [ supportedLocales: const [
Locale('en'), Locale('en'),
Locale('አማ'), Locale(
'am',
),
], ],
path: 'assets/translations', path: 'assets/translations',
startLocale: const Locale('en'), startLocale: const Locale('en'),

View File

@ -21,13 +21,39 @@ class Access {
@JsonKey(name: 'progress_percent') @JsonKey(name: 'progress_percent')
final int? progressPercent; final int? progressPercent;
const Access( @JsonKey(name: 'progress_percent_precise')
{this.reason, final double? progressPercentPrecise;
const Access({
this.reason,
this.totalCount, this.totalCount,
this.isCompleted, this.isCompleted,
this.isAccessible, this.isAccessible,
this.completedCount, this.completedCount,
this.progressPercent}); this.progressPercent,
this.progressPercentPrecise,
});
Access copyWith({
String? reason,
int? totalCount,
bool? isCompleted,
bool? isAccessible,
int? completedCount,
int? progressPercent,
double? progressPercentPrecise,
}) {
return Access(
reason: reason ?? this.reason,
totalCount: totalCount ?? this.totalCount,
isCompleted: isCompleted ?? this.isCompleted,
isAccessible: isAccessible ?? this.isAccessible,
progressPercentPrecise:
progressPercentPrecise ?? this.progressPercentPrecise,
completedCount: completedCount ?? this.completedCount,
progressPercent: progressPercent ?? this.progressPercent,
);
}
factory Access.fromJson(Map<String, dynamic> json) => _$AccessFromJson(json); factory Access.fromJson(Map<String, dynamic> json) => _$AccessFromJson(json);

View File

@ -13,6 +13,8 @@ Access _$AccessFromJson(Map<String, dynamic> json) => Access(
isAccessible: json['is_accessible'] as bool?, isAccessible: json['is_accessible'] as bool?,
completedCount: (json['completed_count'] as num?)?.toInt(), completedCount: (json['completed_count'] as num?)?.toInt(),
progressPercent: (json['progress_percent'] as num?)?.toInt(), progressPercent: (json['progress_percent'] as num?)?.toInt(),
progressPercentPrecise:
(json['progress_percent_precise'] as num?)?.toDouble(),
); );
Map<String, dynamic> _$AccessToJson(Access instance) => <String, dynamic>{ Map<String, dynamic> _$AccessToJson(Access instance) => <String, dynamic>{
@ -22,4 +24,5 @@ Map<String, dynamic> _$AccessToJson(Access instance) => <String, dynamic>{
'is_accessible': instance.isAccessible, 'is_accessible': instance.isAccessible,
'completed_count': instance.completedCount, 'completed_count': instance.completedCount,
'progress_percent': instance.progressPercent, 'progress_percent': instance.progressPercent,
'progress_percent_precise': instance.progressPercentPrecise,
}; };

View File

@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
part 'app_update.g.dart';
@JsonSerializable()
class AppUpdate {
@JsonKey(name: 'force_update')
final bool? forceUpdate;
@JsonKey(name: 'update_available')
final bool? updateAvailable;
const AppUpdate({this.forceUpdate, this.updateAvailable});
factory AppUpdate.fromJson(Map<String, dynamic> json) =>
_$AppUpdateFromJson(json);
Map<String, dynamic> toJson() => _$AppUpdateToJson(this);
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_update.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AppUpdate _$AppUpdateFromJson(Map<String, dynamic> json) => AppUpdate(
forceUpdate: json['force_update'] as bool?,
updateAvailable: json['update_available'] as bool?,
);
Map<String, dynamic> _$AppUpdateToJson(AppUpdate instance) => <String, dynamic>{
'force_update': instance.forceUpdate,
'update_available': instance.updateAvailable,
};

View File

@ -1,39 +1,25 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/module_progress.dart';
import 'access.dart';
part 'course_progress.g.dart'; part 'course_progress.g.dart';
@JsonSerializable() @JsonSerializable()
class CourseProgress { class CourseProgress {
final String? level; final int? id;
final String? title; final String? name;
final String? description; final Access? access;
@JsonKey(name: 'is_locked') final List<ModuleProgress>? modules;
final bool? isLocked;
@JsonKey(name: 'sub_course_id') @JsonKey(name: 'program_id')
final int? courseId; final int? programId;
@JsonKey(name: 'display_order')
final int? displayOrder;
@JsonKey(name: 'progress_status')
final String? progressStatus;
@JsonKey(name: 'progress_percentage')
final double? progressPercentage;
const CourseProgress( const CourseProgress(
{this.level, {this.id, this.name, this.access, this.modules, this.programId});
this.title,
this.isLocked,
this.courseId,
this.description,
this.displayOrder,
this.progressStatus,
this.progressPercentage});
factory CourseProgress.fromJson(Map<String, dynamic> json) => factory CourseProgress.fromJson(Map<String, dynamic> json) =>
_$CourseProgressFromJson(json); _$CourseProgressFromJson(json);

View File

@ -8,24 +8,22 @@ part of 'course_progress.dart';
CourseProgress _$CourseProgressFromJson(Map<String, dynamic> json) => CourseProgress _$CourseProgressFromJson(Map<String, dynamic> json) =>
CourseProgress( CourseProgress(
level: json['level'] as String?, id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?, name: json['name'] as String?,
isLocked: json['is_locked'] as bool?, access: json['access'] == null
courseId: (json['sub_course_id'] as num?)?.toInt(), ? null
description: json['description'] as String?, : Access.fromJson(json['access'] as Map<String, dynamic>),
displayOrder: (json['display_order'] as num?)?.toInt(), modules: (json['modules'] as List<dynamic>?)
progressStatus: json['progress_status'] as String?, ?.map((e) => ModuleProgress.fromJson(e as Map<String, dynamic>))
progressPercentage: (json['progress_percentage'] as num?)?.toDouble(), .toList(),
programId: (json['program_id'] as num?)?.toInt(),
); );
Map<String, dynamic> _$CourseProgressToJson(CourseProgress instance) => Map<String, dynamic> _$CourseProgressToJson(CourseProgress instance) =>
<String, dynamic>{ <String, dynamic>{
'level': instance.level, 'id': instance.id,
'title': instance.title, 'name': instance.name,
'description': instance.description, 'access': instance.access,
'is_locked': instance.isLocked, 'modules': instance.modules,
'sub_course_id': instance.courseId, 'program_id': instance.programId,
'display_order': instance.displayOrder,
'progress_status': instance.progressStatus,
'progress_percentage': instance.progressPercentage,
}; };

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'field_option.g.dart';
@JsonSerializable()
class FieldOption {
final String? code;
final String? label;
const FieldOption({this.code, this.label});
factory FieldOption.fromJson(Map<String, dynamic> json) =>
_$FieldOptionFromJson(json);
Map<String, dynamic> toJson() => _$FieldOptionToJson(this);
}

View File

@ -0,0 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'field_option.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FieldOption _$FieldOptionFromJson(Map<String, dynamic> json) => FieldOption(
code: json['code'] as String?,
label: json['label'] as String?,
);
Map<String, dynamic> _$FieldOptionToJson(FieldOption instance) =>
<String, dynamic>{
'code': instance.code,
'label': instance.label,
};

View File

@ -19,13 +19,32 @@ class LearnCourse {
@JsonKey(name: 'program_id') @JsonKey(name: 'program_id')
final int? programId; final int? programId;
const LearnCourse( const LearnCourse({
{this.id, this.id,
this.name, this.name,
this.access, this.access,
this.programId, this.programId,
this.sortOrder, this.sortOrder,
this.description}); this.description,
});
LearnCourse copyWith({
int? id,
String? name,
Access? access,
int? sortOrder,
int? programId,
String? description,
}) {
return LearnCourse(
id: id ?? this.id,
name: name ?? this.name,
access: access ?? this.access,
sortOrder: sortOrder ?? this.sortOrder,
programId: programId ?? this.programId,
description: description ?? this.description,
);
}
factory LearnCourse.fromJson(Map<String, dynamic> json) => factory LearnCourse.fromJson(Map<String, dynamic> json) =>
_$LearnCourseFromJson(json); _$LearnCourseFromJson(json);

View File

@ -36,6 +36,28 @@ class LearnLesson {
this.description, this.description,
}); });
LearnLesson copyWith({
int? id,
String? title,
int? moduleId,
int? sortOrder,
Access? access,
String? videoUrl,
String? thumbnail,
String? description,
}) {
return LearnLesson(
id: id ?? this.id,
title: title ?? this.title,
access: access ?? this.access,
videoUrl: videoUrl ?? this.videoUrl,
moduleId: moduleId ?? this.moduleId,
sortOrder: sortOrder ?? this.sortOrder,
thumbnail: thumbnail ?? this.thumbnail,
description: description ?? this.description,
);
}
factory LearnLesson.fromJson(Map<String, dynamic> json) => factory LearnLesson.fromJson(Map<String, dynamic> json) =>
_$LearnLessonFromJson(json); _$LearnLessonFromJson(json);

View File

@ -24,15 +24,38 @@ class LearnModule {
@JsonKey(name: 'sort_order') @JsonKey(name: 'sort_order')
final int? sortOrder; final int? sortOrder;
const LearnModule( const LearnModule({
{this.id, this.id,
this.icon, this.icon,
this.name, this.name,
this.access, this.access,
this.courseId, this.courseId,
this.sortOrder, this.sortOrder,
this.programId, this.programId,
this.description}); this.description,
});
LearnModule copyWith({
int? id,
String? icon,
String? name,
int? courseId,
Access? access,
int? programId,
int? sortOrder,
String? description,
}) {
return LearnModule(
id: id ?? this.id,
icon: icon ?? this.icon,
name: name ?? this.name,
access: access ?? this.access,
courseId: courseId ?? this.courseId,
sortOrder: sortOrder ?? this.sortOrder,
programId: programId ?? this.programId,
description: description ?? this.description,
);
}
factory LearnModule.fromJson(Map<String, dynamic> json) => factory LearnModule.fromJson(Map<String, dynamic> json) =>
_$LearnModuleFromJson(json); _$LearnModuleFromJson(json);

View File

@ -16,8 +16,29 @@ class LearnProgram {
@JsonKey(name: 'sort_order') @JsonKey(name: 'sort_order')
final int? sortOrder; final int? sortOrder;
const LearnProgram( const LearnProgram({
{this.id, this.name, this.access, this.sortOrder, this.description}); this.id,
this.name,
this.access,
this.sortOrder,
this.description,
});
LearnProgram copyWith({
int? id,
String? name,
Access? access,
int? sortOrder,
String? description,
}) {
return LearnProgram(
id: id ?? this.id,
name: name ?? this.name,
access: access ?? this.access,
sortOrder: sortOrder ?? this.sortOrder,
description: description ?? this.description,
);
}
factory LearnProgram.fromJson(Map<String, dynamic> json) => factory LearnProgram.fromJson(Map<String, dynamic> json) =>
_$LearnProgramFromJson(json); _$LearnProgramFromJson(json);

View File

@ -0,0 +1,24 @@
import 'package:json_annotation/json_annotation.dart';
import 'access.dart';
part 'lesson_progress.g.dart';
@JsonSerializable()
class LessonProgress {
final int? id;
final String? title;
final Access? access;
@JsonKey(name: 'module_id')
final int? moduleId;
const LessonProgress({this.id, this.title, this.access, this.moduleId});
factory LessonProgress.fromJson(Map<String, dynamic> json) =>
_$LessonProgressFromJson(json);
Map<String, dynamic> toJson() => _$LessonProgressToJson(this);
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'lesson_progress.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LessonProgress _$LessonProgressFromJson(Map<String, dynamic> json) =>
LessonProgress(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
moduleId: (json['module_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$LessonProgressToJson(LessonProgress instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'access': instance.access,
'module_id': instance.moduleId,
};

View File

@ -0,0 +1,36 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/lesson_progress.dart';
import 'access.dart';
part 'module_progress.g.dart';
@JsonSerializable()
class ModuleProgress {
final int? id;
final String? name;
final Access? access;
final List<LessonProgress>? lessons;
@JsonKey(name: 'course_id')
final int? courseId;
@JsonKey(name: 'program_id')
final int? programId;
const ModuleProgress(
{this.id,
this.name,
this.access,
this.lessons,
this.courseId,
this.programId});
factory ModuleProgress.fromJson(Map<String, dynamic> json) =>
_$ModuleProgressFromJson(json);
Map<String, dynamic> toJson() => _$ModuleProgressToJson(this);
}

View File

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'module_progress.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ModuleProgress _$ModuleProgressFromJson(Map<String, dynamic> json) =>
ModuleProgress(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
lessons: (json['lessons'] as List<dynamic>?)
?.map((e) => LessonProgress.fromJson(e as Map<String, dynamic>))
.toList(),
courseId: (json['course_id'] as num?)?.toInt(),
programId: (json['program_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$ModuleProgressToJson(ModuleProgress instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'access': instance.access,
'lessons': instance.lessons,
'course_id': instance.courseId,
'program_id': instance.programId,
};

View File

@ -0,0 +1,24 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/course_progress.dart';
import 'access.dart';
part 'progress_summary.g.dart';
@JsonSerializable()
class ProgressSummary {
final int? id;
final String? name;
final Access? access;
final List<CourseProgress>? courses;
const ProgressSummary({this.id, this.name, this.access, this.courses});
factory ProgressSummary.fromJson(Map<String, dynamic> json) =>
_$ProgressSummaryFromJson(json);
Map<String, dynamic> toJson() => _$ProgressSummaryToJson(this);
}

View File

@ -0,0 +1,27 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'progress_summary.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ProgressSummary _$ProgressSummaryFromJson(Map<String, dynamic> json) =>
ProgressSummary(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
courses: (json['courses'] as List<dynamic>?)
?.map((e) => CourseProgress.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$ProgressSummaryToJson(ProgressSummary instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'access': instance.access,
'courses': instance.courses,
};

View File

@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'refresh_object.g.dart';
@JsonSerializable()
class RefreshObject {
final String? url;
@JsonKey(name: 'object_key')
final String? objectKey;
const RefreshObject({this.url, this.objectKey});
factory RefreshObject.fromJson(Map<String, dynamic> json) =>
_$RefreshObjectFromJson(json);
Map<String, dynamic> toJson() => _$RefreshObjectToJson(this);
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'refresh_object.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
RefreshObject _$RefreshObjectFromJson(Map<String, dynamic> json) =>
RefreshObject(
url: json['url'] as String?,
objectKey: json['object_key'] as String?,
);
Map<String, dynamic> _$RefreshObjectToJson(RefreshObject instance) =>
<String, dynamic>{
'url': instance.url,
'object_key': instance.objectKey,
};

View File

@ -37,11 +37,14 @@ class User {
@JsonKey(name: 'profile_completed') @JsonKey(name: 'profile_completed')
final bool? profileCompleted; final bool? profileCompleted;
@JsonKey(name: 'subscription_status')
final String? subscriptionStatus;
@JsonKey(name: 'profile_picture_url') @JsonKey(name: 'profile_picture_url')
final String? profilePicture; final String? profilePicture;
const User({ const User(
this.email, {this.email,
this.region, this.region,
this.gender, this.gender,
this.userId, this.userId,
@ -55,7 +58,7 @@ class User {
this.profilePicture, this.profilePicture,
this.userInfoLoaded, this.userInfoLoaded,
this.profileCompleted, this.profileCompleted,
}); this.subscriptionStatus});
User copyWith( User copyWith(
{int? userId, {int? userId,
@ -71,7 +74,8 @@ class User {
String? refreshToken, String? refreshToken,
bool? userInfoLoaded, bool? userInfoLoaded,
bool? profileCompleted, bool? profileCompleted,
String? profilePicture}) => String? profilePicture,
String? subscriptionStatus}) =>
User( User(
email: email ?? this.email, email: email ?? this.email,
userId: userId ?? this.userId, userId: userId ?? this.userId,
@ -86,7 +90,8 @@ class User {
refreshToken: refreshToken ?? this.refreshToken, refreshToken: refreshToken ?? this.refreshToken,
userInfoLoaded: userInfoLoaded ?? this.userInfoLoaded, userInfoLoaded: userInfoLoaded ?? this.userInfoLoaded,
profilePicture: profilePicture ?? this.profilePicture, profilePicture: profilePicture ?? this.profilePicture,
profileCompleted: profileCompleted ?? this.profileCompleted); profileCompleted: profileCompleted ?? this.profileCompleted,
subscriptionStatus: subscriptionStatus ?? this.subscriptionStatus);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

View File

@ -21,6 +21,7 @@ User _$UserFromJson(Map<String, dynamic> json) => User(
profilePicture: json['profile_picture_url'] as String?, profilePicture: json['profile_picture_url'] as String?,
userInfoLoaded: json['userInfoLoaded'] as bool?, userInfoLoaded: json['userInfoLoaded'] as bool?,
profileCompleted: json['profile_completed'] as bool?, profileCompleted: json['profile_completed'] as bool?,
subscriptionStatus: json['subscription_status'] as String?,
); );
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
@ -37,5 +38,6 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'access_token': instance.accessToken, 'access_token': instance.accessToken,
'refresh_token': instance.refreshToken, 'refresh_token': instance.refreshToken,
'profile_completed': instance.profileCompleted, 'profile_completed': instance.profileCompleted,
'subscription_status': instance.subscriptionStatus,
'profile_picture_url': instance.profilePicture, 'profile_picture_url': instance.profilePicture,
}; };

View File

@ -1,10 +1,12 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:yimaru_app/models/app_update.dart';
import 'package:yimaru_app/models/learn_lesson.dart'; import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/learn_program.dart';
import 'package:yimaru_app/models/assessment_question.dart'; import 'package:yimaru_app/models/assessment_question.dart';
import 'package:yimaru_app/models/course_catalog.dart'; import 'package:yimaru_app/models/course_catalog.dart';
import 'package:yimaru_app/models/course_lesson.dart'; import 'package:yimaru_app/models/course_lesson.dart';
import 'package:yimaru_app/models/refresh_object.dart';
import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/models/user.dart';
import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/common/app_constants.dart';
@ -12,12 +14,14 @@ import 'package:yimaru_app/ui/common/app_constants.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../models/course_module.dart'; import '../models/course_module.dart';
import '../models/course_unit.dart'; import '../models/course_unit.dart';
import '../models/field_option.dart';
import '../models/learn_course.dart'; import '../models/learn_course.dart';
import '../models/learn_module.dart'; import '../models/learn_module.dart';
import '../models/learn_question.dart'; import '../models/learn_question.dart';
import '../models/learn_subscription.dart'; import '../models/learn_subscription.dart';
import '../models/assessment.dart'; import '../models/assessment.dart';
import '../models/learn_subscription_request.dart'; import '../models/learn_subscription_request.dart';
import '../models/progress_summary.dart';
import '../ui/common/enmus.dart'; import '../ui/common/enmus.dart';
class ApiService { class ApiService {
@ -108,6 +112,34 @@ class ApiService {
} }
} }
// Apple auth
Future<Map<String, dynamic>> appleAuth(Map<String, dynamic> data) async {
try {
Response response = await _service.dio.post(
'$kBaseUrl/$kAppleAuthUrl',
data: data,
);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'message': 'Logged in successfully',
'data': User.fromJson(response.data['data']),
};
} else {
return {
'status': ResponseStatus.failure,
'message': '${response.data['message']}, ${response.data['error']}'
};
}
} on DioException catch (e) {
return {
'status': ResponseStatus.failure,
'message': e.response?.data.toString(),
};
}
}
// Verify otp // Verify otp
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async { Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
try { try {
@ -379,6 +411,222 @@ class ApiService {
} }
} }
// Get educational levels
Future<List<FieldOption>> getEducationalLevels() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=education_level');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['education_level'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get ethiopia regions
Future<List<FieldOption>> getEthiopiaRegions() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=ethiopia_regions');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['ethiopia_regions'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get language challenges
Future<List<FieldOption>> getLanguageChallenges() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=language_challange');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['language_challange'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get occupations
Future<List<FieldOption>> getOccupations() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=occupation');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['occupation'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get age group
Future<List<FieldOption>> getAgeGroups() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=age_group');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['age_group'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get countries
Future<List<FieldOption>> getCountries() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=country');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['country'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get topics
Future<List<FieldOption>> getTopics() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=favourite_topic');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['favourite_topic'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get topics
Future<List<FieldOption>> getLanguageGoals() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=language_goal');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['language_goal'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get learning goal
Future<List<FieldOption>> getLearningGoals() async {
try {
List<FieldOption> levels = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFieldOptions?field_key=learning_goal');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['learning_goal'] as List;
levels = decodedData.map(
(e) {
return FieldOption.fromJson(e);
},
).toList();
return levels;
}
return [];
} catch (e) {
return [];
}
}
// Get assessment questions // Get assessment questions
Future<List<AssessmentQuestion>> getAssessmentQuestions(int id) async { Future<List<AssessmentQuestion>> getAssessmentQuestions(int id) async {
try { try {
@ -403,6 +651,34 @@ class ApiService {
} }
} }
// Complete profile
Future<Map<String, dynamic>> refreshObject(Map<String, dynamic> data) async {
try {
Response response = await _service.dio.post(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFilesUrl/$kRefreshUrl',
data: data,
);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'message': 'Operation successful',
'data': RefreshObject.fromJson(response.data['data'])
};
} else {
return {
'status': ResponseStatus.failure,
'message': 'Unknown Error Occurred'
};
}
} on DioException catch (e) {
return {
'status': ResponseStatus.failure,
'message': e.response?.data.toString(),
};
}
}
// Learn learn programs // Learn learn programs
Future<List<LearnProgram>> getLearnPrograms() async { Future<List<LearnProgram>> getLearnPrograms() async {
try { try {
@ -623,6 +899,30 @@ class ApiService {
} }
} }
// Get progress summary
Future<List<ProgressSummary>> getProgressSummary() async {
try {
List<ProgressSummary> summaries = [];
final Response response = await _service.dio
.get('$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kLmsUrl/$kProgressSummary');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['programs'] as List;
summaries = decodedData.map(
(e) {
return ProgressSummary.fromJson(e);
},
).toList();
return summaries;
}
return [];
} catch (e) {
return [];
}
}
// Complete lesson // Complete lesson
Future<Map<String, dynamic>> completeLearnPractice(int id) async { Future<Map<String, dynamic>> completeLearnPractice(int id) async {
try { try {
@ -680,6 +980,32 @@ class ApiService {
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kSubscribeUrl', '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kSubscribeUrl',
data: data); data: data);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'message': 'Subscription successful!',
'data': LearnSubscriptionRequest.fromJson(response.data['data']),
};
} else {
return {
'status': ResponseStatus.failure,
'message': 'Unknown Error Occurred'
};
}
} on DioException catch (e) {
return {
'status': ResponseStatus.failure,
'message': e.response?.data.toString(),
};
}
}
// Verify subscription
Future<Map<String, dynamic>> verifySubscription(int id) async {
try {
Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kVerifySubscriptionUrl/$id');
if (response.statusCode == 200) { if (response.statusCode == 200) {
return { return {
'message': 'Lesson completed', 'message': 'Lesson completed',
@ -795,4 +1121,29 @@ class ApiService {
return []; return [];
} }
} }
// Check update
Future<Map<String, dynamic>> checkUpdate(Map<String, dynamic> data) async {
try {
Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kAppUrl/$kApiVersionUrl/$kCheckUrl',
data: data,
);
if (response.statusCode == 200) {
return {
'status': ResponseStatus.success,
'data': AppUpdate.fromJson(response.data['data']),
};
} else {
return {
'status': ResponseStatus.failure,
};
}
} on DioException {
return {
'status': ResponseStatus.failure,
};
}
}
} }

View File

@ -0,0 +1,36 @@
import 'dart:io';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'package:stacked/stacked.dart';
class AppleAuthService with ListenableServiceMixin {
AuthorizationCredentialAppleID? _appleCredential;
AuthorizationCredentialAppleID? get appleCredential => _appleCredential;
AppleAuthService() {
listenToReactiveValues([_appleCredential]);
}
bool get isSupported => Platform.isIOS;
Future<void> appleAuth() async {
if (!isSupported) {
throw UnsupportedError('Apple Sign-In is only available on iOS.');
}
_appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
notifyListeners();
}
void logout() {
_appleCredential = null;
notifyListeners();
}
}

View File

@ -2,13 +2,18 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/models/user.dart';
import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'google_auth_service.dart';
import 'localization_service.dart'; import 'localization_service.dart';
class AuthenticationService with ListenableServiceMixin { class AuthenticationService with ListenableServiceMixin {
// Dependency injection // Dependency injection
final _secureService = locator<SecureStorageService>(); final _secureService = locator<SecureStorageService>();
final _googleAuthService = locator<GoogleAuthService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
// User data // User data
@ -18,9 +23,15 @@ class AuthenticationService with ListenableServiceMixin {
// Initialization // Initialization
AuthenticationService() { AuthenticationService() {
listenToReactiveValues([_user, _localizationService]); listenToReactiveValues([_user,_state, _localizationService]);
} }
// Logout state
StateObjects _state = StateObjects.none;
StateObjects get state => _state;
// Check user logged in // Check user logged in
Future<bool> userLoggedIn() async { Future<bool> userLoggedIn() async {
if (await _secureService.getString('userId') != null) { if (await _secureService.getString('userId') != null) {
@ -90,6 +101,8 @@ class AuthenticationService with ListenableServiceMixin {
await _secureService.setBool('userInfoLoaded', true); await _secureService.setBool('userInfoLoaded', true);
await _secureService.setBool( await _secureService.setBool(
'profileCompleted', data.profileCompleted ?? false); 'profileCompleted', data.profileCompleted ?? false);
await _secureService.setString(
'subscriptionStatus', data.subscriptionStatus ?? '');
await _secureService.setString('email', data.email ?? ''); await _secureService.setString('email', data.email ?? '');
await _secureService.setString('region', data.region ?? ''); await _secureService.setString('region', data.region ?? '');
await _secureService.setString('gender', data.gender ?? ''); await _secureService.setString('gender', data.gender ?? '');
@ -113,6 +126,7 @@ class AuthenticationService with ListenableServiceMixin {
accessToken: _user?.accessToken, accessToken: _user?.accessToken,
refreshToken: _user?.refreshToken, refreshToken: _user?.refreshToken,
profileCompleted: data.profileCompleted, profileCompleted: data.profileCompleted,
subscriptionStatus: data.subscriptionStatus,
); );
notifyListeners(); notifyListeners();
@ -169,19 +183,27 @@ class AuthenticationService with ListenableServiceMixin {
userInfoLoaded: await _secureService.getBool('userInfoLoaded'), userInfoLoaded: await _secureService.getBool('userInfoLoaded'),
profilePicture: await _secureService.getString('profilePicture'), profilePicture: await _secureService.getString('profilePicture'),
profileCompleted: await _secureService.getBool('profileCompleted'), profileCompleted: await _secureService.getBool('profileCompleted'),
subscriptionStatus: await _secureService.getString('subscriptionStatus'),
); );
return _user; return _user;
} }
// Logout // Logout
Future<void> logout() async { Future<void> logout() async {
_state = StateObjects.logout;
notifyListeners();
bool firstTimeInstall = await isFirstTimeInstall(); bool firstTimeInstall = await isFirstTimeInstall();
String language = await _localizationService.selectedLanguage['code']; String language = await _localizationService.selectedLanguage['code'];
_user = null; _user = null;
await _secureService.clear(); await _secureService.clear();
await _googleAuthService.logout();
await setFirstTimeInstall(firstTimeInstall); await setFirstTimeInstall(firstTimeInstall);
await _secureService.setString('language', language); await _secureService.setString('language', language);
_state = StateObjects.none;
notifyListeners(); notifyListeners();
} }
} }

View File

@ -27,7 +27,7 @@ class DioService {
DioService() { DioService() {
_dio.options _dio.options
..baseUrl = kBaseUrl ..baseUrl = kBaseUrl
..connectTimeout = const Duration(seconds: 5) ..connectTimeout = const Duration(seconds: 10)
..receiveTimeout = const Duration(seconds: 15); ..receiveTimeout = const Duration(seconds: 15);
_dio.interceptors.add( _dio.interceptors.add(

View File

@ -19,7 +19,6 @@ class GoogleAuthService with ListenableServiceMixin {
Future<void> logout() async { Future<void> logout() async {
await _signIn.signOut(); await _signIn.signOut();
_googleUser = null; _googleUser = null;
notifyListeners();
} }
// Google authentication // Google authentication
@ -30,10 +29,7 @@ class GoogleAuthService with ListenableServiceMixin {
_googleUser ??= _googleUser ??=
await _signIn.authenticate(scopeHint: ['email', 'profile']); await _signIn.authenticate(scopeHint: ['email', 'profile']);
print('GOOGLE AUTH');
print(_googleUser?.email);
print(_googleUser?.displayName);
print(_googleUser?.authentication.idToken);
}); });
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {

View File

@ -5,39 +5,53 @@ import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../ui/common/app_constants.dart'; import '../models/refresh_object.dart';
import '../ui/common/enmus.dart';
import 'api_service.dart';
import 'dio_service.dart'; import 'dio_service.dart';
class ImageDownloaderService { class ImageDownloaderService {
// Dependency injection // Dependency injection
final _service = locator<DioService>(); final _service = locator<DioService>();
// Image downloader final _apiService = locator<ApiService>();
Future<String> downloader(String? networkImage) async {
late File image;
late String profileImage; final Dio _dio = Dio();
// Image downloader
Future<String?> downloader(String? networkImage) async {
try {
File? image;
String? profileImage = networkImage;
final Directory appDir = await getApplicationDocumentsDirectory(); final Directory appDir = await getApplicationDocumentsDirectory();
if (networkImage != null) { Map<String, dynamic> data = {'reference': profileImage};
profileImage = networkImage.contains('https://lh3.googleusercontent.com') Map<String, dynamic> response = await _apiService.refreshObject(data);
? networkImage
: '$kBaseUrl$networkImage'; if (response['status'] == ResponseStatus.success) {
RefreshObject object = response['data'] as RefreshObject;
profileImage = object.url;
} }
final Response profileImageResponse = await _service.dio.get( if (profileImage != null) {
final Response profileImageResponse = await _dio.get<List<int>>(
profileImage, profileImage,
options: Options( options: Options(
followRedirects: false,
responseType: ResponseType.bytes, responseType: ResponseType.bytes,
), ),
); );
final imageName = basename(networkImage ?? ''); final localImagePath = join(appDir.path, 'profile.jpg');
final localImagePath = join(appDir.path, imageName);
image = File(localImagePath); image = File(localImagePath);
image.writeAsBytes(profileImageResponse.data); image.writeAsBytes(profileImageResponse.data);
return image.path; return image.path;
} }
return null;
} catch (e) {
return null;
}
}
} }

View File

@ -1,62 +1,51 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:in_app_update/in_app_update.dart'; import 'package:in_app_update/in_app_update.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:storage_info/storage_info.dart';
import 'package:yimaru_app/app/app.locator.dart'; import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/models/app_update.dart';
import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../ui/common/ui_helpers.dart'; import '../ui/common/ui_helpers.dart';
import 'api_service.dart';
class InAppUpdateService { class InAppUpdateService {
// Dependency Injection
final _apiService = locator<ApiService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
Future<int> getBatteryLevel() async { final _statusCheckerService = locator<StatusCheckerService>();
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
Future<void> checkForUpdate() async { Future<void> checkForUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// KewedeConst().showErrorToast(
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// KewedeConst()
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// KewedeConst()
// .showErrorToast('Unable to update app, please free up space.');
}
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
}
try { try {
final info = await InAppUpdate.checkForUpdate(); final info = await InAppUpdate.checkForUpdate();
if (info.updateAvailability == UpdateAvailability.updateAvailable) { final String version = await _statusCheckerService.getAppVersion();
AppUpdateResult result = await InAppUpdate.performImmediateUpdate();
if (version != info.availableVersionCode.toString()) {
Map<String, dynamic> data = {
'platform': 'ANDROID',
'version_code': info.availableVersionCode
};
Map<String, dynamic> response = await _apiService.checkUpdate(data);
if (response['status'] == ResponseStatus.success) {
AppUpdate update = response['data'] as AppUpdate;
if (update.updateAvailable ?? false) {
if (update.forceUpdate ?? false) {
AppUpdateResult result =
await InAppUpdate.performImmediateUpdate();
if (result == AppUpdateResult.userDeniedUpdate) { if (result == AppUpdateResult.userDeniedUpdate) {
showErrorToast('An update is required to continue using this app.'); showErrorToast(
'An update is required to continue using this app.');
_navigationService.back(); _navigationService.back();
} }
} else {
await InAppUpdate.startFlexibleUpdate();
}
}
}
} }
// ... rest of your update logic ...
} on PlatformException { } on PlatformException {
// Handle specific error code for better user experience and potentially different error messages for each issue // Handle specific error code for better user experience and potentially different error messages for each issue
} }

View File

@ -1,10 +1,16 @@
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/course_progress.dart';
import 'package:yimaru_app/models/module_progress.dart';
import 'package:yimaru_app/models/refresh_object.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../models/access.dart';
import '../models/learn_course.dart'; import '../models/learn_course.dart';
import '../models/learn_lesson.dart'; import '../models/learn_lesson.dart';
import '../models/learn_module.dart'; import '../models/learn_module.dart';
import '../models/learn_program.dart'; import '../models/learn_program.dart';
import '../models/progress_summary.dart';
import 'api_service.dart'; import 'api_service.dart';
class LearnService with ListenableServiceMixin { class LearnService with ListenableServiceMixin {
@ -13,9 +19,25 @@ class LearnService with ListenableServiceMixin {
// Initialization // Initialization
learnService() { learnService() {
listenToReactiveValues([_programs, _lessons, _modules]); listenToReactiveValues(
[_programs, _courses, _lessons, _modules, _totalCount]);
} }
// Completed count
int _completedCount = 0;
int get completedCount => _completedCount;
// Total count
int _totalCount = 0;
int get totalCount => _totalCount;
// Total progress
int _totalProgress = 0;
int get totalProgress => _totalProgress;
// Learn program // Learn program
List<LearnProgram> _programs = []; List<LearnProgram> _programs = [];
@ -36,6 +58,21 @@ class LearnService with ListenableServiceMixin {
List<LearnLesson> get lessons => _lessons; List<LearnLesson> get lessons => _lessons;
// Learn programs
Future<String?> refreshObject(String url) async {
Map<String, dynamic> data = {'reference': url};
Map<String, dynamic> response = await _apiService.refreshObject(data);
if (response['status'] == ResponseStatus.success) {
RefreshObject object = response['data'] as RefreshObject;
return object.url ?? '';
}
return null;
}
// Learn programs // Learn programs
Future<void> getLearnPrograms() async { Future<void> getLearnPrograms() async {
_programs = await _apiService.getLearnPrograms(); _programs = await _apiService.getLearnPrograms();
@ -63,4 +100,118 @@ class LearnService with ListenableServiceMixin {
_lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
notifyListeners(); notifyListeners();
} }
// Learn progress
Future<void> getLearnProgress() async {
final summaries = await _apiService.getProgressSummary();
/// PROGRAM ACCESS MAP
final Map<int, Access?> programAccessMap = {};
/// COURSE ACCESS MAP
final Map<int, Access?> courseAccessMap = {};
/// MODULE ACCESS MAP
final Map<int, Access?> moduleAccessMap = {};
/// LESSON ACCESS MAP
final Map<int, Access?> lessonAccessMap = {};
// Build maps
for (final summary in summaries) {
if (summary.id != null) {
programAccessMap[summary.id!] = summary.access;
}
for (final course in summary.courses ?? []) {
if (course.id != null) {
courseAccessMap[course.id!] = course.access;
}
for (final module in course.modules ?? []) {
if (module.id != null) {
moduleAccessMap[module.id!] = module.access;
}
for (final lesson in module.lessons ?? []) {
if (lesson.id != null) {
lessonAccessMap[lesson.id!] = lesson.access;
}
}
}
}
}
/// UPDATE PROGRAMS
_programs = _programs.map((program) {
return program.copyWith(
access: programAccessMap[program.id] ?? program.access,
);
}).toList();
/// UPDATE COURSES
_courses = _courses.map((course) {
return course.copyWith(
access: courseAccessMap[course.id] ?? course.access,
);
}).toList();
/// UPDATE MODULES
_modules = _modules.map((module) {
return module.copyWith(
access: moduleAccessMap[module.id] ?? module.access,
);
}).toList();
/// UPDATE LESSONS
_lessons = _lessons.map((lesson) {
return lesson.copyWith(
access: lessonAccessMap[lesson.id] ?? lesson.access,
);
}).toList();
notifyListeners();
}
LearnModule? getLearnModuleById(int id) {
try {
return _modules.firstWhere((e) => e.id == id);
} catch (_) {
return null;
}
}
LearnCourse? getLearnCourseById(int id) {
try {
return _courses.firstWhere((e) => e.id == id);
} catch (_) {
return null;
}
}
Future<void> getProgressSummary() async {
final summaries = await _apiService.getProgressSummary();
for (final ProgressSummary summary in summaries) {
_totalCount = _totalCount + (summary.access?.totalCount ?? 0);
_completedCount = _completedCount + (summary.access?.completedCount ?? 0);
for (final CourseProgress course in summary.courses ?? []) {
_totalCount = _totalCount + (course.access?.totalCount ?? 0);
_completedCount =
_completedCount + (course.access?.completedCount ?? 0);
for (final ModuleProgress module in course.modules ?? []) {
_totalCount = _totalCount + (module.access?.totalCount ?? 0);
_completedCount =
_completedCount + (module.access?.completedCount ?? 0);
}
}
}
_totalProgress = summaries.fold(
0, (sum, progress) => sum + (progress.access?.progressPercent ?? 0));
notifyListeners();
}
} }

View File

@ -23,7 +23,7 @@ class LocalizationService with ListenableServiceMixin {
Map<String, dynamic> get selectedLanguage => _selectedLanguage; Map<String, dynamic> get selectedLanguage => _selectedLanguage;
final List<Map<String, dynamic>> _languages = [ final List<Map<String, dynamic>> _languages = [
{'code': 'አማ', 'language': 'አማርኛ'}, {'code': 'am', 'language': 'አማርኛ'},
{'code': 'en', 'language': 'English'}, {'code': 'en', 'language': 'English'},
]; ];
@ -37,7 +37,7 @@ class LocalizationService with ListenableServiceMixin {
required Map<String, dynamic> title}) async { required Map<String, dynamic> title}) async {
_selectedLanguage = title; _selectedLanguage = title;
if (title['code'] == 'አማ') { if (title['code'] == 'am') {
await setAmharicLanguage(context); await setAmharicLanguage(context);
} else { } else {
await setEnglishLanguage(context); await setEnglishLanguage(context);
@ -50,17 +50,15 @@ class LocalizationService with ListenableServiceMixin {
if (language == 'en') { if (language == 'en') {
_selectedLanguage = {'code': 'en', 'language': 'English'}; _selectedLanguage = {'code': 'en', 'language': 'English'};
} else { } else {
_selectedLanguage = {'code': 'አማ', 'language': 'አማርኛ'}; _selectedLanguage = {'code': 'am', 'language': 'አማርኛ'};
} }
notifyListeners(); notifyListeners();
print('SELECTED LANGUAGE: $language $_selectedLanguage');
} }
Future<void> setAmharicLanguage(BuildContext context) async { Future<void> setAmharicLanguage(BuildContext context) async {
await context.setLocale(const Locale('አማ')); await context.setLocale(const Locale('am'));
await _secureService.setString('language', 'አማ'); await _secureService.setString('language', 'am');
notifyListeners(); notifyListeners();
} }

View File

@ -130,7 +130,7 @@ class NotificationService {
} }
Future<void> updateFCMToken() async { Future<void> updateFCMToken() async {
print('DEVICE TOKEN: ${await _messaging.getToken()}'); // print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) { _messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken); // updateTokenOnServer(newToken);
}); });

View File

@ -0,0 +1,115 @@
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/field_option.dart';
import '../app/app.locator.dart';
import 'api_service.dart';
class OnboardingService with ListenableServiceMixin {
// Dependency injection
final _apiService = locator<ApiService>();
// Initialization
learnService() {
listenToReactiveValues([
_topics,
_regions,
_ageGroups,
_countries,
_challenges,
_occupations,
_learningGoals,
_languageGoals,
_educationalBackgrounds
]);
}
// Topics
List<FieldOption> _topics = [];
List<FieldOption> get topics => _topics;
// Regions
List<FieldOption> _regions = [];
List<FieldOption> get regions => _regions;
// Age groups
List<FieldOption> _ageGroups = [];
List<FieldOption> get ageGroups => _ageGroups;
// Countries
List<FieldOption> _countries = [];
List<FieldOption> get countries => _countries;
// Challenges
List<FieldOption> _challenges = [];
List<FieldOption> get challenges => _challenges;
// Occupations
List<FieldOption> _occupations = [];
List<FieldOption> get occupations => _occupations;
// Learning goals
List<FieldOption> _learningGoals = [];
List<FieldOption> get learningGoals => _learningGoals;
// Language goals
List<FieldOption> _languageGoals = [];
List<FieldOption> get languageGoals => _languageGoals;
// Educational backgrounds
List<FieldOption> _educationalBackgrounds = [];
List<FieldOption> get educationalBackgrounds => _educationalBackgrounds;
// Onboarding fields
Future<bool> getOnboardingFields() async {
_topics = await _apiService.getTopics();
_ageGroups = await _apiService.getAgeGroups();
_countries = await _apiService.getCountries();
_occupations = await _apiService.getOccupations();
_regions = await _apiService.getEthiopiaRegions();
_learningGoals = await _apiService.getLearningGoals();
_languageGoals = await _apiService.getLanguageGoals();
_challenges = await _apiService.getLanguageChallenges();
_educationalBackgrounds = await _apiService.getEducationalLevels();
notifyListeners();
if (_topics.isNotEmpty &&
_ageGroups.isNotEmpty &&
_countries.isNotEmpty &&
_occupations.isNotEmpty &&
_regions.isNotEmpty &&
_learningGoals.isNotEmpty &&
_challenges.isNotEmpty &&
_educationalBackgrounds.isNotEmpty) {
return true;
}
return false;
}
// Profile detail fields
Future<void> getProfileDetailFields() async {
_countries = await _apiService.getCountries();
_occupations = await _apiService.getOccupations();
_regions = await _apiService.getEthiopiaRegions();
notifyListeners();
}
}

View File

@ -1,4 +1,5 @@
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:yimaru_app/services/secure_storage_service.dart'; import 'package:yimaru_app/services/secure_storage_service.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
@ -25,4 +26,16 @@ class StatusCheckerService {
return false; return false;
} }
} }
// Get app version
Future<String> getAppVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version; // e.g. 1.0.0
String buildNumber = packageInfo.buildNumber; // version code
print("Version: $version");
print("Build Number: $buildNumber");
return buildNumber;
}
} }

View File

@ -1,10 +1,18 @@
// Endpoints // Endpoints
String kBaseUrl = 'https://api.yimaruacademy.com'; String kBaseUrl = 'https://api.yimaruacademy.com';
String kLmsUrl = 'lms';
String kAppUrl = 'app';
String kApiUrl = 'api'; String kApiUrl = 'api';
String kUnitsUrl = 'units'; String kUnitsUrl = 'units';
String kCheckUrl = 'check';
String kFilesUrl = 'files';
String kApiVersionUrl = 'v1'; String kApiVersionUrl = 'v1';
String kLevelsUrl = 'levels'; String kLevelsUrl = 'levels';
@ -15,6 +23,8 @@ String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons'; String kLessonsUrl = 'lessons';
String kVersionUrl = 'version';
String kProgramsUrl = 'programs'; String kProgramsUrl = 'programs';
String kRegisterUrl = 'register'; String kRegisterUrl = 'register';
@ -25,13 +35,15 @@ String kCompleteUrl = 'complete';
String kPaymentsUrl = 'payments'; String kPaymentsUrl = 'payments';
String kExamPrepUrl = 'exam-prep';
String kSubscribeUrl = 'subscribe'; String kSubscribeUrl = 'subscribe';
String kPracticesUrl = 'practices'; String kPracticesUrl = 'practices';
String kQuestionsUrl = 'questions'; String kQuestionsUrl = 'questions';
String kExamPrepUrl = 'exam-prep'; String kRefreshUrl = 'refresh-url';
String kCoursePractice = 'by-owner'; String kCoursePractice = 'by-owner';
@ -47,14 +59,20 @@ String kSubmodulesUrl = 'sub-modules';
String kSubcoursesUrl = 'sub-courses'; String kSubcoursesUrl = 'sub-courses';
String kFieldOptions = 'field-options';
String kResetPassword = 'resetPassword'; String kResetPassword = 'resetPassword';
String kVerifySubscriptionUrl = 'verify';
String kQuestionSetsUrl = 'question-sets'; String kQuestionSetsUrl = 'question-sets';
String kRequestResetCode = 'sendResetCode'; String kRequestResetCode = 'sendResetCode';
String kSubcategoriesUrl = 'sub-categories'; String kSubcategoriesUrl = 'sub-categories';
String kProgressSummary = 'progress-summary';
String kPublishedVideos = 'videos/published'; String kPublishedVideos = 'videos/published';
String kCoursePracticeQuestions = 'questions'; String kCoursePracticeQuestions = 'questions';
@ -83,6 +101,8 @@ String kLessonProgressUrl = 'api/v1/progress/videos';
String kGoogleAuthUrl = 'api/v1/auth/google/android'; String kGoogleAuthUrl = 'api/v1/auth/google/android';
String kAppleAuthUrl = 'api/v1/auth/apple';
String kCourseProgressUrl = 'api/v1/progress/courses'; String kCourseProgressUrl = 'api/v1/progress/courses';
String kAssessmentsUrl = 'api/v1/assessment/questions'; String kAssessmentsUrl = 'api/v1/assessment/questions';
@ -104,7 +124,6 @@ String kTelegramSupport = '@yimaruacademy2026';
String kTelegramSupportLink = 'https://t.me/yimaruacademy2026'; String kTelegramSupportLink = 'https://t.me/yimaruacademy2026';
String kErrorUrl = 'https://yimaru.net/api/v1/payments/arifpay/error'; String kErrorUrl = 'https://api.yimaruacademy.com/payment/error';
String kSuccessUrl = String kSuccessUrl = 'https://api.yimaruacademy.com/payment/success';
'https://api.yimaruacademy.com/api/v1/payments/arifpay/success';

View File

@ -5,10 +5,10 @@ enum Voice { sample, recorded }
enum ResponseStatus { success, failure } enum ResponseStatus { success, failure }
// Login method // Login method
enum LoginMethod { phone, email, google } enum LoginMethod { phone, email, google, apple }
// Sign-up method // Sign-up method
enum SignUpMethod { phone, email, google } enum SignUpMethod { phone, email, google, apple }
// Learn practice // Learn practice
enum LearnPractices { course, module, lesson } enum LearnPractices { course, module, lesson }
@ -25,9 +25,13 @@ enum ProgressStatuses { pending, started, completed }
// Duolingo types // Duolingo types
enum DuolingoAssessments { speaking, reading, writing, listening } enum DuolingoAssessments { speaking, reading, writing, listening }
// Practice reason
enum PracticeReason { course, module, lesson, previousModule, previousCourse }
// State object // State object
enum StateObjects { enum StateObjects {
none, none,
logout,
courses, courses,
register, register,
verifyOtp, verifyOtp,
@ -39,6 +43,8 @@ enum StateObjects {
learnModules, learnModules,
learnCourses, learnCourses,
profileImage, profileImage,
paymentStatus,
profileDetail,
learnPrograms, learnPrograms,
courseLessons, courseLessons,
profileUpdate, profileUpdate,
@ -50,11 +56,14 @@ enum StateObjects {
loginWithGoogle, loginWithGoogle,
loadLessonVideo, loadLessonVideo,
loadCourseVideo, loadCourseVideo,
progressSummary,
requestResetCode, requestResetCode,
profileCompletion, profileCompletion,
learnSubscription, learnSubscription,
learnSubscriptions, learnSubscriptions,
loginWithApple,
registerWithGoogle, registerWithGoogle,
registerWithApple,
learnPracticeSample, learnPracticeSample,
learnPracticeAnswer, learnPracticeAnswer,
loginWithPhoneNumber, loginWithPhoneNumber,

View File

@ -14,23 +14,224 @@ class CodegenLoader extends AssetLoader{
return Future.value(mapLocales[locale.toString()]); return Future.value(mapLocales[locale.toString()]);
} }
static const Map<String,dynamic> _en = {
"loading": "Loading",
"welcome_back": "Welcome back",
"checking_user_info": "Checking user info",
"dont_have_account": "Don't have an account?",
"email": "Email",
"password": "Password",
"forgot_password": "Forgot password?",
"cont": "Continue",
"register": "Register",
"login_with_google": "Login with Google",
"login_with_apple": "Login with Apple",
"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_apple": "Register with Apple",
"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",
"eight_character_minimum": "8 characters minimum",
"password_match": "password match",
"sign_up_agreement": "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"terms_of_services": "Terms of Service",
"and": "and",
"privacy_policy": "Privacy Policy",
"register_with_email": "Register with email",
"verification_code": "Verification Code",
"resend_code": "Resend Code",
"code_sent_to_phone": "Code sent to your number",
"code_sent_to_email": "Code sent to your email",
"resend_code_in": "Resend code in",
"reset_password": "Reset Password",
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
"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",
"continue_learning": "Continue Learning",
"start_learning": "Start Learning",
"completed": "Completed",
"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",
"select_region": "Select region",
"enter_your_city": "Enter your city",
"occupation": "Occupation",
"select_occupation": "Select occupation",
"save_changes": "Save changes",
"my_progress": "My progress",
"track_your_achievement": "Track your achievements and learning streak",
"account_and_privacy": "Account & Privacy",
"manage_settings": "Manage settings and app preference",
"support": "Support",
"get_help": "Get help through phone or Telegram",
"logout": "Logout",
"app_settings": "App settings",
"legal_and_information": "Legal & Information",
"change_language": "Change language",
"terms_and_conditions": "Terms & Conditions",
"delete_account": "Delete account",
"language_preference": "Language preference",
"choose_your_language": "Choose your language",
"switch_language_anytime": "You can switch languages anytime",
"need_help": "Need help?",
"call_support": "Call support",
"talk_with_support": "Talk with our support team directly",
"telegram_support": "Telegram support",
"chat_via_telegram": "Chat instantly via Telegram",
"call_our_support": "Call our support team between 9 AM - 6 PM",
"tap_to_call": "Tap to call",
"join_telegram": "Join Yimaru Academy on Telegram",
"connect_with_support_team": "Connect with our support team instantly on Telegram for quick assistance and community updates",
"open_in_telegram": "Open in Telegram",
"search_for": "Search for",
"current_level": "Current Level",
"keep_up_the_great_work": "Keep up the great work! You're doing amazing.",
"no_practice_available": "No practice available!",
"begin_module_practice": "Begin Module Practice",
"lets_practice_lesson": "Lets Practice",
"lets_quickly_review": "Lets quickly review what youve learned in this module!",
"lets_practice_module": "Let's practice what you just learnt!",
"ask_you_few_actions": "Ill ask you a few questions, and you can respond naturally.",
"begin_level_practice": "Begin Level Practice",
"lets_practice_course": "Lets Practice Course",
"lets_quick_review": "Lets quickly review what youve learned in this level!",
"speaking": "is speaking...",
"you_have_finished_practice": "You have finished your practice",
"view_results": "View My Results",
"sample_answer": "Sample Answer",
"your_answer": "Your Answer",
"sound_confident": "You sound more confident this time - great improvement!",
"you_have_completed": "Yay, youve completed",
"yes": "Yes",
"no": "No",
"want_to_quit": "Are you sure you want to quit?",
"required_field": "The field is required",
"enter_full_name": "Enter your full name",
"invalid_email": "Invalid email format",
"phone_must_start_with": "Phone number must start with 251",
"phone_must_be": "Phone number must be 12 digits",
"what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender.",
"age_range": "Which age range are you in?",
"age_for_personalization": "Well personalize your learning experience based on your age.",
"educational_background": "Whats your current educational level?",
"education_for_personalization": "This helps us tailor your lessons to your experience.",
"your_occupation": "Whats your occupation?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.",
"location": "Where are you from?",
"select_country_region": "Select your country and region from the dropdown",
"select_country": "Select country",
"learning_goal": "Choose your learning goal.",
"language_goal": "Whats your main goal for improving your English?",
"your_goal": "Your goal helps us tailor your learning journey.",
"write_your_goal": "Write your goal…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"write_your_challenge": "Write your challenge…",
"topic_interest": "Which topics interest you most?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.",
"your_interest": "Write your interest…",
"want_quick_assessment": "Want a quick assessment to know your English level?",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.",
"skip": "Skip",
"finish_level": "Finish Level",
"likely_speaker": "Youre likely speaker of",
"great_job": "Great Job! Heres your next step to keep improving.",
"lets_start_practice": "Let's start your practice",
"welcome_abroad": "Welcome aboard",
"ready_to_explore": "Youre ready to explore your personalized lessons.",
"finish": "Finish",
"finish_all_practice_lesson": "Finish the previous lesson practice to take this practice",
"finish_all_practice_module": "Finish the lesson practices to take the Module Practice",
"finish_all_practice_course": "Finish the Module practices to take the Course practice",
"finish_all_practice_previouse_module": "Finish the previous Module practice to take this practice",
"finish_all_practice_previouse_course": "Finish the previous course practice to take this",
"track_journey": "Track your learning journey and see your growth over time.",
"learn_english": "Learn English",
"keep_momentum": "Great job! Keep the momentum.",
"completed_practices": "Completed Practices",
"total_practices": "Total Practices",
"progress_percentage": "Progress Percentage"
};
static const Map<String,dynamic> _am = { static const Map<String,dynamic> _am = {
"loading": "በመጫን ላይ", "loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ", "welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም? ይመዝገቡ", "dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል", "email": "ኢሜይል",
"password": "የይለፍ ቃል", "password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?", "forgot_password": "የይለፍ ቃል ረሱ?",
"cont": "ቀጥል", "cont": "ቀጥል",
"register": "ይመዝገቡ", "register": "ይመዝገቡ",
"login_with_google": "በጉግል ይግቡ", "login_with_google": "በጉግል ይግቡ",
"login_with_apple": "በአፕል ይግቡ",
"or": "ወይም", "or": "ወይም",
"login_with_phone": "በስልክ ቁጥር ይግቡ", "login_with_phone": "በስልክ ቁጥር ይግቡ",
"create_account": "አዲስ መለያ ይፍጠሩ", "create_account": "አዲስ መለያ ይፍጠሩ",
"already_have_account": "መለያ አለዎት?", "already_have_account": "መለያ አለዎት?",
"login": " ይግቡ ", "login": " ይግቡ ",
"register_with_google": "በጉግል ይመዝገቡ", "register_with_google": "በጉግል ይመዝገቡ",
"register_with_apple": "በአፕል ይመዝገቡ",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ", "register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።", "enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"login_with_email": "በኢሜይል ይግቡ", "login_with_email": "በኢሜይል ይግቡ",
@ -150,7 +351,7 @@ class CodegenLoader extends AssetLoader{
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር", "begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_practice": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው", "speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ", "view_results": "ውጤቶቼን እይ",
@ -169,164 +370,47 @@ class CodegenLoader extends AssetLoader{
"what_should_we_call_you": "ምን ብለን እንጠራህ?", "what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ", "choose_your_gender": "ጾታህን ምረጥ",
"gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።" "gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?",
"age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?",
"education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።",
"your_occupation": "ስራህ ምንድን ነው?",
"occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"location": "ከየት ነህ?",
"select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ",
"select_country": "አገር ምረጥ",
"learning_goal": "የመማር ዓላማህን ምረጥ",
"language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?",
"your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።",
"write_your_goal": "ዓላማህን ጻፍ…",
"challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"your_interest": "ፍላጎትህን ጻፍ…",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
"skip": "ዝለል",
"finish_level": "ደረጃውን አጠናቅቅ",
"likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ",
"great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።",
"lets_start_practice": "ልምምድህን እንጀምር",
"welcome_abroad": "እንኳን ደህና መጣህ",
"ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።",
"finish": "አጠናቅቅ",
"finish_all_practice_lesson": "ይህን ልምምድ ለመውሰድ የቀድሞውን የትምህርት ልምምድ ያጠናቅቁ",
"finish_all_practice_module": "የሞጁሉን ልምምድ ለመውሰድ የትምህርት ልምምዶችን ያጠናቅቁ",
"finish_all_practice_course": "የኮርሱን ልምምድ ለመውሰድ የሞጁል ልምምዶችን ያጠናቅቁ",
"finish_all_practice_previouse_module": "ይህን ልምምድ ለመውሰድ የቀድሞውን የሞጁል ልምምድ ያጠናቅቁ",
"finish_all_practice_previouse_course": "ይህን ለመውሰድ የቀድሞውን የኮርስ ልምምድ ያጠናቅቁ",
"track_journey": "የትምህርት ጉዞዎን ይከታተሉ እና በጊዜ ሂደት ያሳዩትን እድገት ይመልከቱ።",
"learn_english": "እንግሊዝኛ ይማሩ",
"keep_momentum": "በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።",
"completed_practices": "የተጠናቀቁ ልምምዶች",
"total_practices": "ጠቅላላ ልምምዶች",
"progress_percentage": "የእድገት መቶኛ"
}; };
static const Map<String,dynamic> _en = { static const Map<String, Map<String,dynamic>> mapLocales = {"en": _en, "am": _am};
"loading": "Loading",
"welcome_back": "Welcome back",
"checking_user_info": "Checking user info",
"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",
"eight_character_minimum": "8 characters minimum",
"password_math": "password match",
"sign_up_agreement": "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"terms_of_services": "Terms of Service",
"and": "and",
"privacy_policy": "Privacy Policy",
"register_with_email": "Register with email",
"verification_code": "Verification Code",
"resend_code": "Resend Code",
"code_sent_to_phone": "Code sent to your number",
"code_sent_to_email": "Code sent to your email",
"resend_code_in": "Resend code in",
"reset_password": "Reset Password",
"enter_email_reset_code": "Enter your email. We will send you a reset code.",
"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",
"continue_learning": "Continue Learning",
"start_learning": "Start Learning",
"completed": "Completed",
"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",
"select_region": "Select region",
"enter_your_city": "Enter your city",
"occupation": "Occupation",
"select_occupation": "Select occupation",
"save_changes": "Save changes",
"my_progress": "My progress",
"track_your_achievement": "Track your achievements and learning streak",
"account_and_privacy": "Account & Privacy",
"manage_settings": "Manage settings and app preference",
"support": "Support",
"get_help": "Get help through phone or Telegram",
"logout": "Logout",
"app_settings": "App settings",
"legal_and_information": "Legal & Information",
"change_language": "Change language",
"terms_and_conditions": "Terms & Conditions",
"delete_account": "Delete account",
"language_preference": "Language preference",
"choose_your_language": "Choose your language",
"switch_language_anytime": "You can switch languages anytime",
"need_help": "Need help?",
"call_support": "Call support",
"talk_with_support": "Talk with our support team directly",
"telegram_support": "Telegram support",
"chat_via_telegram": "Chat instantly via Telegram",
"call_our_support": "Call our support team between 9 AM - 6 PM",
"tap_to_call": "Tap to call",
"join_telegram": "Join Yimaru Academy on Telegram",
"connect_with_support_team": "Connect with our support team instantly on Telegram for quick assistance and community updates",
"open_in_telegram": "Open in Telegram",
"search_for": "Search for",
"current_level": "Current Level",
"keep_up_the_great_work": "Keep up the great work! You\\'re doing amazing.",
"no_practice_available": "No practice available!",
"begin_module_practice": "Begin Module Practice",
"lets_practice_lesson": "Lets Practice",
"lets_quickly_review": "Lets quickly review what youve learned in this module!",
"lets_practice_module": "Let's practice what you just learnt!",
"ask_you_few_actions": "Ill ask you a few questions, and you can respond naturally.",
"begin_level_practice": "Begin Level Practice",
"lets_practice_course": "Lets Practice Course",
"lets_quick_review": "Lets quickly review what youve learned in this level!",
"speaking": "is speaking...",
"you_have_finished_practice": "You have finished your practice",
"view_results": "View My Results",
"sample_answer": "Sample Answer",
"your_answer": "Your Answer",
"sound_confident": "You sound more confident this time - great improvement!",
"you_have_completed": "Yay, youve completed",
"yes": "Yes",
"no": "No",
"want_to_quit": "Are you sure you want to quit?",
"required_field": "The field is required",
"enter_full_name": "Enter your full name",
"invalid_email": "Invalid email format",
"phone_must_start_with": "Phone number must start with 251",
"phone_must_be": "Phone number must be 12 digits",
"what_should_we_call_you": "What should we call you?",
"name_for_personalization": "Well use your name to personalize your learning journey.",
"choose_your_gender": "Choose your gender?",
"gender_for_personalization": "Well personalize your learning experience based on your gender."
};
static const Map<String, Map<String,dynamic>> mapLocales = {"am": _am, "en": _en};
} }

View File

@ -13,12 +13,14 @@ abstract class LocaleKeys {
static const cont = 'cont'; static const cont = 'cont';
static const register = 'register'; static const register = 'register';
static const login_with_google = 'login_with_google'; static const login_with_google = 'login_with_google';
static const login_with_apple = 'login_with_apple';
static const or = 'or'; static const or = 'or';
static const login_with_phone = 'login_with_phone'; static const login_with_phone = 'login_with_phone';
static const create_account = 'create_account'; static const create_account = 'create_account';
static const already_have_account = 'already_have_account'; static const already_have_account = 'already_have_account';
static const login = 'login'; static const login = 'login';
static const register_with_google = 'register_with_google'; static const register_with_google = 'register_with_google';
static const register_with_apple = 'register_with_apple';
static const register_with_phone = 'register_with_phone'; static const register_with_phone = 'register_with_phone';
static const enter_phone_number = 'enter_phone_number'; static const enter_phone_number = 'enter_phone_number';
static const login_with_email = 'login_with_email'; static const login_with_email = 'login_with_email';
@ -43,10 +45,10 @@ abstract class LocaleKeys {
static const reset_code = 'reset_code'; static const reset_code = 'reset_code';
static const new_password = 'new_password'; static const new_password = 'new_password';
static const logged_in_successfully = 'logged_in_successfully'; static const logged_in_successfully = 'logged_in_successfully';
static const view_course = 'view_course';
static const continue_learning = 'continue_learning'; static const continue_learning = 'continue_learning';
static const start_learning = 'start_learning'; static const start_learning = 'start_learning';
static const completed = 'completed'; static const completed = 'completed';
static const view_course = 'view_course';
static const take_practice = 'take_practice'; static const take_practice = 'take_practice';
static const your_current_level = 'your_current_level'; static const your_current_level = 'your_current_level';
static const overall_progress = 'overall_progress'; static const overall_progress = 'overall_progress';
@ -136,7 +138,7 @@ abstract class LocaleKeys {
static const ask_you_few_actions = 'ask_you_few_actions'; static const ask_you_few_actions = 'ask_you_few_actions';
static const begin_level_practice = 'begin_level_practice'; static const begin_level_practice = 'begin_level_practice';
static const lets_practice_course = 'lets_practice_course'; static const lets_practice_course = 'lets_practice_course';
static const lets_quick_practice = 'lets_quick_practice'; static const lets_quick_review = 'lets_quick_review';
static const speaking = 'speaking'; static const speaking = 'speaking';
static const you_have_finished_practice = 'you_have_finished_practice'; static const you_have_finished_practice = 'you_have_finished_practice';
static const view_results = 'view_results'; static const view_results = 'view_results';
@ -156,5 +158,45 @@ abstract class LocaleKeys {
static const name_for_personalization = 'name_for_personalization'; static const name_for_personalization = 'name_for_personalization';
static const choose_your_gender = 'choose_your_gender'; static const choose_your_gender = 'choose_your_gender';
static const gender_for_personalization = 'gender_for_personalization'; static const gender_for_personalization = 'gender_for_personalization';
static const age_range = 'age_range';
static const age_for_personalization = 'age_for_personalization';
static const educational_background = 'educational_background';
static const education_for_personalization = 'education_for_personalization';
static const your_occupation = 'your_occupation';
static const occupation_for_personalization = 'occupation_for_personalization';
static const location = 'location';
static const select_country_region = 'select_country_region';
static const select_country = 'select_country';
static const learning_goal = 'learning_goal';
static const language_goal = 'language_goal';
static const your_goal = 'your_goal';
static const write_your_goal = 'write_your_goal';
static const challenge_you_face = 'challenge_you_face';
static const evey_one_has_strugle = 'evey_one_has_strugle';
static const write_your_challenge = 'write_your_challenge';
static const topic_interest = 'topic_interest';
static const favourite_topic = 'favourite_topic';
static const your_interest = 'your_interest';
static const want_quick_assessment = 'want_quick_assessment';
static const answer_quick_questions = 'answer_quick_questions';
static const skip = 'skip';
static const finish_level = 'finish_level';
static const likely_speaker = 'likely_speaker';
static const great_job = 'great_job';
static const lets_start_practice = 'lets_start_practice';
static const welcome_abroad = 'welcome_abroad';
static const ready_to_explore = 'ready_to_explore';
static const finish = 'finish';
static const finish_all_practice_lesson = 'finish_all_practice_lesson';
static const finish_all_practice_module = 'finish_all_practice_module';
static const finish_all_practice_course = 'finish_all_practice_course';
static const finish_all_practice_previouse_module = 'finish_all_practice_previouse_module';
static const finish_all_practice_previouse_course = 'finish_all_practice_previouse_course';
static const track_journey = 'track_journey';
static const learn_english = 'learn_english';
static const keep_momentum = 'keep_momentum';
static const completed_practices = 'completed_practices';
static const total_practices = 'total_practices';
static const progress_percentage = 'progress_percentage';
} }

View File

@ -27,7 +27,13 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
Widget _buildScaffoldWrapper(AccountPrivacyViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AccountPrivacyViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(AccountPrivacyViewModel viewModel) =>
Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
); );
Widget _buildScaffold(AccountPrivacyViewModel viewModel) => Widget _buildScaffold(AccountPrivacyViewModel viewModel) =>

View File

@ -12,8 +12,7 @@ class AccountPrivacyViewModel extends ReactiveViewModel {
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices => [_localizationService];
[ _localizationService];
// Languages // Languages
Map<String, dynamic> get _selectedLanguage => Map<String, dynamic> get _selectedLanguage =>

View File

@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'arif_pay_viewmodel.dart';
class ArifPayView extends StackedView<ArifPayViewModel> {
final String phone;
const ArifPayView({Key? key, required this.phone}) : super(key: key);
void _pop(ArifPayViewModel viewModel) => viewModel.pop;
Future<void> _error() async {
// await Navigator.pushNamed(context, AppRoutes.subscriptionErrorPage);
// Navigation.pop();
}
void _success() {
// Navigation.navigateTo(
// AppRoutes.subscriptionSuccessPage,
// arguments: widget.body,
// );
}
@override
void onViewModelReady(ArifPayViewModel viewModel) async {
await viewModel.createLearnSubscriptionRequest(phone);
super.onViewModelReady(viewModel);
}
@override
ArifPayViewModel viewModelBuilder(BuildContext context) => ArifPayViewModel();
@override
Widget builder(
BuildContext context,
ArifPayViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(ArifPayViewModel viewModel) =>
Scaffold(body: _buildScaffoldState(viewModel));
Widget _buildScaffoldState(ArifPayViewModel viewModel) =>
viewModel.busy(StateObjects.learnSubscription)
? const PageLoadingIndicator()
: _buildScaffold(viewModel);
Widget _buildScaffold(ArifPayViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(ArifPayViewModel viewModel) => InAppWebView(
initialUrlRequest:
URLRequest(url: WebUri(viewModel.request?.paymentUrl ?? '')),
onUpdateVisitedHistory: (controller, url, androidIsReload) {
if (url
.toString()
.contains("https://checkout.arifpay.net/canceled")) {
showErrorToast('Operation was cancelled');
// _pop();
} else if (url.toString().contains(kSuccessUrl)) {
_success();
} else if (url.toString().contains(kErrorUrl)) {
showErrorToast('Operation was cancelled');
// _pop();
} else if (url.toString().contains("http://x.com/elonmusk/status/")) {
_error();
} else if (url.toString().contains(kErrorUrl)) {
_error();
}
},
);
}

View File

@ -1,71 +0,0 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../models/learn_subscription_request.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
class ArifPayViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn subscription request
LearnSubscriptionRequest? _request;
LearnSubscriptionRequest? get request => _request;
// Navigation
void pop() => _navigationService.back();
// Remote api call
// Learn subscription
Future<void> createLearnSubscriptionRequest(String phone) async =>
await runBusyFuture(_createLearnSubscriptionRequest(phone),
busyObject: StateObjects.learnSubscription);
Future<void> _createLearnSubscriptionRequest(String phone) async {
if (await _statusChecker.checkConnection()) {
Map<String, dynamic> data = {
'plan_id': 1,
'phone': '251$phone',
'email': 'test@gmail.com'
};
Map<String, dynamic> response =
await _apiService.createSubscriptionRequest(data);
if (response['status'] == ResponseStatus.success) {
_request = response['data'];
}
}
}
//
// Future<void> verifyLearnSubscription(String id) async => await runBusyFuture(_verifyLearnSubscription(phone),
// busyObject: StateObjects.learnSubscription);
//
// Future<void> _verifyLearnSubscription(String id) async {
// if (await _statusChecker.checkConnection()) {
// Map<String,dynamic> data = {
// 'plan_id':1,
// 'phone': '251$phone',
// 'email':'test@gmail.com'
// };
//
// Map<String, dynamic> response =
// await _apiService.createSubscriptionRequest(data);
//
// if (response['status'] == ResponseStatus.success) {
// _request = response['data'];
// }
// }
// }
}

View File

@ -52,9 +52,9 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
]; ];
Widget _buildAssessmentIntroWrapper(AssessmentViewModel viewModel) => Widget _buildAssessmentIntroWrapper(AssessmentViewModel viewModel) =>
viewModel.busy(StateObjects.assessments) || viewModel.assessments.isEmpty viewModel.busy(StateObjects.assessments)
? _buildPageLoadingIndicator(viewModel) ? _buildPageLoadingIndicator(viewModel)
: _buildAssessmentIntro(); : _buildAssessmentIntro(viewModel);
Widget _buildPageLoadingIndicator(AssessmentViewModel viewModel) => Widget _buildPageLoadingIndicator(AssessmentViewModel viewModel) =>
AssessmentLoadingScreen( AssessmentLoadingScreen(
@ -64,7 +64,10 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
onPop: viewModel.assessments.isEmpty ? viewModel.pop : null, onPop: viewModel.assessments.isEmpty ? viewModel.pop : null,
); );
Widget _buildAssessmentIntro() => const AssessmentIntroScreen(); Widget _buildAssessmentIntro(AssessmentViewModel viewModel) =>
AssessmentIntroScreen(
hasAssessments: viewModel.assessments.isNotEmpty,
);
Widget _buildAssessment() => const AssessmentQuestionsScreen(); Widget _buildAssessment() => const AssessmentQuestionsScreen();

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -8,7 +10,9 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../assessment_viewmodel.dart'; import '../assessment_viewmodel.dart';
class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> { class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
const AssessmentIntroScreen({super.key}); final bool hasAssessments;
const AssessmentIntroScreen({super.key, required this.hasAssessments});
Future<void> _next(AssessmentViewModel viewModel) async => Future<void> _next(AssessmentViewModel viewModel) async =>
viewModel.setFirstAssessment(); viewModel.setFirstAssessment();
@ -27,11 +31,8 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
_buildAppBar(viewModel), [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
Expanded(child: _buildBodyWrapper(viewModel)); Expanded(child: _buildBodyWrapper(viewModel));
@ -57,7 +58,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
); );
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceLarge,
_buildTitle(), _buildTitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildSubtitle(), _buildSubtitle(),
@ -67,17 +68,19 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Want a quick assessment to know your English level?', LocaleKeys.want_quick_assessment.tr(),
style: style25DG600, style: style25DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Answer a few quick questions to help us understand your English proficiency.', LocaleKeys.answer_quick_questions.tr(),
style: style14MG400, style: style14MG400,
); );
@ -87,7 +90,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
); );
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
_buildContinueButton(viewModel), if (hasAssessments) _buildContinueButton(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildSkipButtonWrapper(viewModel) _buildSkipButtonWrapper(viewModel)
]; ];
@ -96,9 +99,9 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
safe: false, safe: false,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _next(viewModel), onTap: () async => await _next(viewModel),
); );
@ -111,9 +114,9 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildSkipButton(AssessmentViewModel viewModel) => Widget _buildSkipButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Skip',
borderRadius: 12, borderRadius: 12,
backgroundColor: kcWhite, backgroundColor: kcWhite,
text: LocaleKeys.skip.tr(),
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => viewModel.next(page: 3), onTap: () => viewModel.next(page: 3),

View File

@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart'; import 'package:yimaru_app/ui/widgets/custom_small_radio_button.dart';
@ -134,8 +136,8 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: viewModel.currentQuestionIndex == text: viewModel.currentQuestionIndex ==
viewModel.assessmentQuestions.length - 1 viewModel.assessmentQuestions.length - 1
? 'Finish Level' ? LocaleKeys.finish_level.tr()
: 'Continue', : LocaleKeys.cont.tr(),
backgroundColor: viewModel.selectedAnswers backgroundColor: viewModel.selectedAnswers
.containsKey('${viewModel.currentQuestionIndex + 1}') .containsKey('${viewModel.currentQuestionIndex + 1}')
? kcPrimaryColor ? kcPrimaryColor

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -25,18 +27,17 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
_buildAppBar(viewModel), [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -75,13 +76,13 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
]; ];
Widget _buildTitle(AssessmentViewModel viewModel) => Text( Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Youre likely a ${viewModel.proficiencyLevel?.toUpperCase()} speaker!', '${LocaleKeys.likely_speaker.tr()} ${viewModel.proficiencyLevel?.toUpperCase()}',
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildPrimarySubtitle() => Text( Widget _buildPrimarySubtitle() => Text(
'Great Job! Heres your next step to keep improving.', LocaleKeys.great_job.tr(),
style: style14MG400, style: style14MG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -93,7 +94,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg'); 'assets/icons/${viewModel.proficiencyLevel?.substring(0, 1).toLowerCase()}_${viewModel.proficiencyLevel?.substring(1).toLowerCase()}.svg');
Widget _buildSecondarySubtitle() => Text( Widget _buildSecondarySubtitle() => Text(
'Let\'s start your practice', LocaleKeys.lets_start_practice.tr(),
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
@ -106,9 +107,9 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.cont.tr(),
onTap: () => viewModel.next(), onTap: () => viewModel.next(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );

View File

@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
@ -42,18 +44,17 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
_buildAppBar(viewModel), [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: true, showBackButton: true,
onPop: viewModel.goBack, onPop: viewModel.goBack,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -92,7 +93,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich( Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
TextSpan( TextSpan(
text: 'Welcome aboard', text: LocaleKeys.welcome_abroad.tr(),
style: style25DG600, style: style25DG600,
children: [ children: [
TextSpan( TextSpan(
@ -104,7 +105,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Youre ready to explore your personalized lessons.', LocaleKeys.ready_to_explore.tr(),
style: style14MG400, style: style14MG400,
); );
@ -116,9 +117,9 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Finish',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
text: LocaleKeys.finish.tr(),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _start(viewModel), onTap: () async => await _start(viewModel),
); );

View File

@ -28,9 +28,13 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
Widget _buildScaffoldWrapper(CallSupportViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(CallSupportViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffoldContainer(viewModel),
); );
Widget _buildScaffoldContainer(CallSupportViewModel viewModel) => Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);
Widget _buildScaffold(CallSupportViewModel viewModel) => Widget _buildScaffold(CallSupportViewModel viewModel) =>
SafeArea(child: _buildBodyWrapper(viewModel)); SafeArea(child: _buildBodyWrapper(viewModel));

View File

@ -88,7 +88,6 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
required ForgetPasswordViewModel viewModel}) => required ForgetPasswordViewModel viewModel}) =>
[ [
_buildAppBar(viewModel), _buildAppBar(viewModel),
verticalSpaceMedium,
_buildExpandedBody(context: context, viewModel: viewModel) _buildExpandedBody(context: context, viewModel: viewModel)
]; ];
@ -96,8 +95,10 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -86,18 +86,17 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(ForgetPasswordViewModel viewModel) => [ List<Widget> _buildScaffoldChildren(ForgetPasswordViewModel viewModel) =>
_buildAppBar(viewModel), [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
verticalSpaceMedium,
_buildExpandedBody(viewModel)
];
Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar(
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) => Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>

View File

@ -2,16 +2,18 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../widgets/coming_soon.dart'; import '../../widgets/coming_soon.dart';
import '../course/course_view.dart';
import 'home_viewmodel.dart'; import 'home_viewmodel.dart';
class HomeView extends StackedView<HomeViewModel> { class HomeView extends StackedView<HomeViewModel> {
const HomeView({Key? key}) : super(key: key); const HomeView({Key? key}) : super(key: key);
@override @override
void onViewModelReady(HomeViewModel viewModel) async { void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.inAppUpdate(); await viewModel.inAppUpdate();
@ -24,7 +26,13 @@ class HomeView extends StackedView<HomeViewModel> {
@override @override
Widget builder( Widget builder(
BuildContext context, HomeViewModel viewModel, Widget? child) => BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffold(viewModel); KeyedSubtree(
key: ValueKey(context.locale.languageCode),
child: _buildScaffoldStack(viewModel));
Widget _buildScaffoldStack(HomeViewModel viewModel) => Stack(
children: [_buildScaffold(viewModel), _buildLogoutState(viewModel)],
);
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold( Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentPage), body: getViewForIndex(viewModel.currentPage),
@ -78,4 +86,9 @@ class HomeView extends StackedView<HomeViewModel> {
return const ProfileView(); return const ProfileView();
} }
} }
Widget _buildLogoutState(HomeViewModel viewModel) =>
viewModel.state == StateObjects.logout
? const PageLoadingIndicator()
: Container();
} }

View File

@ -5,6 +5,7 @@ import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/app_strings.dart'; import 'package:yimaru_app/ui/common/app_strings.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/in_app_update_service.dart'; import '../../../services/in_app_update_service.dart';
@ -12,8 +13,13 @@ import '../../../services/in_app_update_service.dart';
class HomeViewModel extends ReactiveViewModel { class HomeViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
final _statusChecker = locator<StatusCheckerService>(); final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>(); final _bottomSheetService = locator<BottomSheetService>();
final _inAppUpdateService = locator<InAppUpdateService>(); final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@override @override
@ -25,6 +31,11 @@ class HomeViewModel extends ReactiveViewModel {
User? get user => _user; User? get user => _user;
// Logout state
StateObjects get _state => _authenticationService.state;
StateObjects get state => _state;
// Bottom navigation // Bottom navigation
int _currentPage = 0; int _currentPage = 0;
@ -44,6 +55,8 @@ class HomeViewModel extends ReactiveViewModel {
rebuildUi(); rebuildUi();
} }
// Remote api calls // Remote api calls
// In-app update // In-app update

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart'; import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart';
@ -23,19 +24,21 @@ class LandingView extends StackedView<LandingViewModel> {
LandingViewModel viewModel, LandingViewModel viewModel,
Widget? child, Widget? child,
) => ) =>
_buildLandingScreens(viewModel); _buildLandingScreensWrapper(viewModel);
Widget _buildLandingScreensWrapper(LandingViewModel viewModel) => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildLandingScreens(viewModel),
);
Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel( Widget _buildLandingScreens(LandingViewModel viewModel) => FlutterCarousel(
options: FlutterCarouselOptions( options: FlutterCarouselOptions(
autoPlay: true, autoPlay: true,
viewportFraction: 1, viewportFraction: 1,
showIndicator: true,
indicatorMargin: 40, indicatorMargin: 40,
showIndicator: false,
height: double.maxFinite, height: double.maxFinite,
slideIndicator: CircularSlideIndicator( controller: viewModel.controller,
slideIndicatorOptions:
const SlideIndicatorOptions(indicatorRadius: 2.5),
),
), ),
items: _buildScreens(), items: _buildScreens(),
); );

View File

@ -1,3 +1,4 @@
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@ -11,6 +12,16 @@ class LandingViewModel extends BaseViewModel {
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
// Controller
final FlutterCarouselController _controller = FlutterCarouselController();
FlutterCarouselController get controller => _controller;
// In-app navigation
void next() {
_controller.nextPage();
}
// Navigation // Navigation
Future<void> navigateToLogin() async => Future<void> navigateToLogin() async =>
await _navigationService.replaceWithLoginView(); await _navigationService.replaceWithLoginView();
@ -18,6 +29,7 @@ class LandingViewModel extends BaseViewModel {
// Remote api call // Remote api call
// First time install // First time install
Future<void> setFirstTimeInstall() async { Future<void> setFirstTimeInstall() async {
await runBusyFuture(_setFirstTimeInstall()); await runBusyFuture(_setFirstTimeInstall());
} }

View File

@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
backgroundColor: kcWhite, backgroundColor: kcWhite,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -122,15 +122,15 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel); viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel);
Widget _buildIndicator() => Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcPrimaryColor);
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
text: 'Get Started', onTap: viewModel.next,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

@ -29,9 +29,12 @@ class LanguageViewModel extends ReactiveViewModel {
Future<void> setSelectedLanguage( Future<void> setSelectedLanguage(
{required BuildContext context, {required BuildContext context,
required Map<String, dynamic> title}) async => required Map<String, dynamic> title}) async {
await _localizationService.setSelectedLanguage( await _localizationService.setSelectedLanguage(
context: context, title: title); context: context, title: title);
rebuildUi();
}
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();

View File

@ -6,14 +6,48 @@ import '../../common/app_colors.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart'; import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/finish_practice_sheet.dart';
import '../../widgets/learn_course_tile.dart'; import '../../widgets/learn_course_tile.dart';
import '../../widgets/small_app_bar.dart'; import '../../widgets/small_app_bar.dart';
import 'learn_course_viewmodel.dart'; import 'learn_course_viewmodel.dart';
class LearnCourseView extends StackedView<LearnCourseViewModel> { class LearnCourseView extends StackedView<LearnCourseViewModel> {
final int id; final int id;
const LearnCourseView({Key? key, required this.id}) : super(key: key); const LearnCourseView({Key? key, required this.id}) : super(key: key);
Future<void> _onPractice(
{required BuildContext context,
required LearnCourse course,
required LearnCourseViewModel viewModel}) async {
if (course.access?.completedCount == course.access?.totalCount) {
await viewModel.navigateToLearnPractice(
id: course.id ?? 0, level: course.name ?? '');
} else {
if (course.access?.isAccessible ?? false) {
await _showSheet(
context: context,
viewModel: viewModel,
practice: PracticeReason.course);
} else {
await _showSheet(
context: context,
viewModel: viewModel,
practice: PracticeReason.previousCourse);
}
}
}
Future<void> _showSheet(
{required BuildContext context,
required PracticeReason practice,
required LearnCourseViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel: viewModel, practice: practice),
);
@override @override
void onViewModelReady(LearnCourseViewModel viewModel) async { void onViewModelReady(LearnCourseViewModel viewModel) async {
await viewModel.getLearnCourses(id); await viewModel.getLearnCourses(id);
@ -30,32 +64,46 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
LearnCourseViewModel viewModel, LearnCourseViewModel viewModel,
Widget? child, Widget? child,
) => ) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper(LearnCourseViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffoldContainer(viewModel), body: _buildScaffoldContainer(context: context, viewModel: viewModel),
); );
Widget _buildScaffoldContainer(LearnCourseViewModel viewModel) => Container( Widget _buildScaffoldContainer(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Container(
decoration: bgDecoration, decoration: bgDecoration,
child: _buildScaffold(viewModel), child: _buildScaffold(context: context, viewModel: viewModel),
); );
Widget _buildScaffold(LearnCourseViewModel viewModel) => Widget _buildScaffold(
SafeArea(child: _buildBody(viewModel)); {required BuildContext context,
required LearnCourseViewModel viewModel}) =>
SafeArea(child: _buildBody(context: context, viewModel: viewModel));
Widget _buildBody(LearnCourseViewModel viewModel) => Padding( Widget _buildBody(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel), child: _buildColumn(context: context, viewModel: viewModel),
); );
Widget _buildColumn(LearnCourseViewModel viewModel) => Column( Widget _buildColumn(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildCoursesColumnWrapper(viewModel) _buildCoursesColumnWrapper(context: context, viewModel: viewModel)
], ],
); );
@ -64,34 +112,46 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
showBackButton: true, showBackButton: true,
); );
Widget _buildCoursesColumnWrapper(LearnCourseViewModel viewModel) => Widget _buildCoursesColumnWrapper(
Expanded(child: _buildLevelsColumnScrollView(viewModel)); {required BuildContext context,
required LearnCourseViewModel viewModel}) =>
Expanded(
child: _buildLevelsColumnScrollView(
context: context, viewModel: viewModel));
Widget _buildLevelsColumnScrollView(LearnCourseViewModel viewModel) => Widget _buildLevelsColumnScrollView(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildListViewBuilder(viewModel), child: _buildListViewBuilder(context: context, viewModel: viewModel),
); );
Widget _buildListViewBuilder(LearnCourseViewModel viewModel) => Widget _buildListViewBuilder(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
viewModel.busy(StateObjects.learnCourses) viewModel.busy(StateObjects.learnCourses)
? _buildProgressIndicator() ? _buildProgressIndicator()
: _buildListView(viewModel); : _buildListView(context: context, viewModel: viewModel);
Widget _buildProgressIndicator() => const Center( Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor), child: CustomCircularProgressIndicator(color: kcPrimaryColor),
); );
Widget _buildListView(LearnCourseViewModel viewModel) => ListView.separated( Widget _buildListView(
{required BuildContext context,
required LearnCourseViewModel viewModel}) =>
ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.courses.length, itemCount: viewModel.courses.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
course: viewModel.courses[index], course: viewModel.courses[index],
onPracticeTap: () async => await _onPractice(
context: context,
viewModel: viewModel,
course: viewModel.courses[index]),
onViewTap: () async => onViewTap: () async =>
await viewModel.navigateToLearnModule(viewModel.courses[index]), await viewModel.navigateToLearnModule(viewModel.courses[index]),
onPracticeTap: () async => await viewModel.navigateToLearnPractice(
id: viewModel.courses[index].id ?? 0,
level: viewModel.courses[index].name ?? ''),
), ),
separatorBuilder: (context, index) => verticalSpaceSmall, separatorBuilder: (context, index) => verticalSpaceSmall,
); );
@ -106,4 +166,12 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
onViewTap: onViewTap, onViewTap: onViewTap,
onPracticeTap: onPracticeTap, onPracticeTap: onPracticeTap,
); );
Widget _buildSheet(
{required PracticeReason practice,
required LearnCourseViewModel viewModel}) =>
FinishPracticeSheet(
practice: practice,
onTap: viewModel.pop,
);
} }

View File

@ -29,6 +29,9 @@ class LearnCourseViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnModule(LearnCourse course) async => Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course); _navigationService.navigateToLearnModuleView(course: course);
@ -39,7 +42,7 @@ class LearnCourseViewModel extends ReactiveViewModel {
level: level, level: level,
practice: LearnPractices.course, practice: LearnPractices.course,
label: LocaleKeys.begin_level_practice.tr(), label: LocaleKeys.begin_level_practice.tr(),
subtitle: LocaleKeys.lets_quick_practice.tr(), subtitle: LocaleKeys.lets_quick_review.tr(),
title: '${LocaleKeys.lets_practice_course.tr()} $level', title: '${LocaleKeys.lets_practice_course.tr()} $level',
); );

View File

@ -12,6 +12,7 @@ import 'package:yimaru_app/ui/widgets/motivation_card.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart'; import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/finish_practice_sheet.dart';
import '../../widgets/small_app_bar.dart'; import '../../widgets/small_app_bar.dart';
import 'learn_lesson_viewmodel.dart'; import 'learn_lesson_viewmodel.dart';
@ -20,6 +21,44 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
const LearnLessonView({Key? key, required this.module}) : super(key: key); const LearnLessonView({Key? key, required this.module}) : super(key: key);
Future<void> _onPractice(
{required int index,
required LearnLesson lesson,
required BuildContext context,
required LearnLessonViewModel viewModel}) async {
/* if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
}*/
if (index > 1) {
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'subscribed') {
if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
}
} else {
await viewModel.navigateToLearnSubscription();
}
} else {
if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
}
}
}
Future<void> _showSheet(
{required BuildContext context,
required LearnLessonViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel),
);
@override @override
void onViewModelReady(LearnLessonViewModel viewModel) async { void onViewModelReady(LearnLessonViewModel viewModel) async {
await viewModel.getLessons(module.id ?? 0); await viewModel.getLessons(module.id ?? 0);
@ -124,13 +163,13 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
verticalSpaceTiny, verticalSpaceTiny,
_buildSubtitle(), _buildSubtitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildModuleProgress(), _buildModuleProgress(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
_buildMotivationCard(), _buildMotivationCard(),
verticalSpaceLarge, verticalSpaceLarge,
_buildHeader(), _buildHeader(),
verticalSpaceMedium, verticalSpaceMedium,
_buildListViewBuilder(viewModel), _buildListViewBuilder(context: context, viewModel: viewModel),
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -143,10 +182,19 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
style: style14DG500, style: style14DG500,
); );
Widget _buildModuleProgress() => ModuleProgress( Widget _buildModuleProgress(LearnLessonViewModel viewModel) => ModuleProgress(
total: module.access?.totalCount ?? 0, total: (viewModel.getUpdatedLearnModule(module.id ?? 0) ?? module)
completed: module.access?.completedCount ?? 0, .access
progress: module.access?.progressPercent ?? 0, ?.totalCount ??
0,
completed: (viewModel.getUpdatedLearnModule(module.id ?? 0) ?? module)
.access
?.completedCount ??
0,
progress: (viewModel.getUpdatedLearnModule(module.id ?? 0) ?? module)
.access
?.progressPercent ??
0,
); );
Widget _buildMotivationCard() => const MotivationCard(); Widget _buildMotivationCard() => const MotivationCard();
@ -156,42 +204,60 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
style: style18DG700, style: style18DG700,
); );
Widget _buildListViewBuilder(LearnLessonViewModel viewModel) => Widget _buildListViewBuilder(
{required BuildContext context,
required LearnLessonViewModel viewModel}) =>
viewModel.busy(StateObjects.learnLessons) viewModel.busy(StateObjects.learnLessons)
? _buildProgressIndicator() ? _buildProgressIndicator()
: _buildListView(viewModel); : _buildListView(context: context, viewModel: viewModel);
Widget _buildProgressIndicator() => const Center( Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor), child: CustomCircularProgressIndicator(color: kcPrimaryColor),
); );
Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder( Widget _buildListView(
{required BuildContext context,
required LearnLessonViewModel viewModel}) =>
ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.lessons.length, itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
index: index, index: index,
lesson: viewModel.lessons[index], lesson: viewModel.lessons[index],
last: index == viewModel.lessons.length - 1,
onPracticeTap: () async => await _onPractice(
index: index,
context: context,
viewModel: viewModel,
lesson: viewModel.lessons[index],
),
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail( onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
index: index,
module: module, module: module,
lesson: viewModel.lessons[index], lesson: viewModel.lessons[index],
hasPractice: hasPractice:
index != viewModel.lessons.length - 1 ? true : false), index != viewModel.lessons.length - 1 ? true : false),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.lessons[index].id ?? 0),
), ),
); );
Widget _buildTile({ Widget _buildTile({
required bool last,
required int index, required int index,
required LearnLesson lesson, required LearnLesson lesson,
required GestureTapCallback? onLessonTap, required GestureTapCallback? onLessonTap,
required GestureTapCallback? onPracticeTap, required GestureTapCallback? onPracticeTap,
}) => }) =>
LearnLessonTile( LearnLessonTile(
last: last,
index: index, index: index,
lesson: lesson, lesson: lesson,
onLessonTap: onLessonTap, onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap, onPracticeTap: onPracticeTap,
); );
Widget _buildSheet(LearnLessonViewModel viewModel) => FinishPracticeSheet(
onTap: viewModel.pop,
practice: PracticeReason.lesson,
);
} }

View File

@ -8,8 +8,11 @@ import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/learn_module.dart'; import '../../../models/learn_module.dart';
import '../../../models/user.dart';
import '../../../services/authentication_service.dart';
import '../../../services/learn_service.dart'; import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/helper_functions.dart';
class LearnLessonViewModel extends ReactiveViewModel { class LearnLessonViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
@ -19,10 +22,23 @@ class LearnLessonViewModel extends ReactiveViewModel {
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => [_learnService]; List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Learn lessons // Learn lessons
final Map<int, String> _refreshedThumbnails = {};
Map<int, String> get refreshedThumbnails => _refreshedThumbnails;
List<LearnLesson> get _lessons => _learnService.lessons; List<LearnLesson> get _lessons => _learnService.lessons;
List<LearnLesson> get lessons => _lessons; List<LearnLesson> get lessons => _lessons;
@ -39,12 +55,19 @@ class LearnLessonViewModel extends ReactiveViewModel {
subtitle: LocaleKeys.ask_you_few_actions.tr(), subtitle: LocaleKeys.ask_you_few_actions.tr(),
); );
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnLessonDetail( Future<void> navigateToLearnLessonDetail(
{required bool hasPractice, {required int index,
required bool hasPractice,
required LearnLesson lesson, required LearnLesson lesson,
required LearnModule module}) async => required LearnModule module}) async =>
await _navigationService.navigateToLearnLessonDetailView( await _navigationService.navigateToLearnLessonDetailView(
lesson: lesson, module: module, hasPractice: hasPractice); index: index,
lesson: lesson,
module: module,
hasPractice: hasPractice);
// Remote api call // Remote api call
@ -55,6 +78,32 @@ class LearnLessonViewModel extends ReactiveViewModel {
Future<void> _getLessons(int id) async { Future<void> _getLessons(int id) async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
await _learnService.getLearnLessons(id); await _learnService.getLearnLessons(id);
// await refreshLessonImages(_lessons);
}
}
// Get module
LearnModule? getUpdatedLearnModule(int id) {
return _learnService.getLearnModuleById(id);
}
//Refresh image
Future<void> refreshLessonImages(List<LearnLesson> lessons) async {
for (final lesson in lessons) {
final thumbnail = lesson.thumbnail;
if (lesson.id == null || thumbnail == null || thumbnail.isEmpty) {
continue;
}
final String? refreshedUrl = await _learnService.refreshObject(thumbnail);
if (refreshedUrl != null) {
_refreshedThumbnails[lesson.id!] = refreshedUrl;
} }
} }
} }
String getLessonImage(LearnLesson lesson) =>
getReadableUrl(_refreshedThumbnails[lesson.id] ?? '') ?? '';
}

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import '../../common/app_colors.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart'; import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/finish_practice_sheet.dart';
import '../../widgets/small_app_bar.dart'; import '../../widgets/small_app_bar.dart';
import 'learn_module_viewmodel.dart'; import 'learn_module_viewmodel.dart';
@ -19,6 +20,41 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
const LearnModuleView({Key? key, required this.course}) : super(key: key); const LearnModuleView({Key? key, required this.course}) : super(key: key);
Future<void> _onPractice(
{required BuildContext context,
required LearnModule module,
required LearnModuleViewModel viewModel}) async {
if (module.access?.completedCount == module.access?.totalCount) {
await viewModel.navigateToLearnPractice(
id: module.id ?? 0, module: module.name ?? '');
} else {
if (module.access?.isAccessible ?? false) {
print('Accessible');
await _showSheet(
context: context,
viewModel: viewModel,
practice: PracticeReason.module);
} else {
print('Inaccessible');
await _showSheet(
context: context,
viewModel: viewModel,
practice: PracticeReason.previousModule);
}
}
}
Future<void> _showSheet(
{required BuildContext context,
required PracticeReason practice,
required LearnModuleViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel: viewModel, practice: practice),
);
@override @override
void onViewModelReady(LearnModuleViewModel viewModel) async { void onViewModelReady(LearnModuleViewModel viewModel) async {
await viewModel.getLearnModules(course.id ?? 0); await viewModel.getLearnModules(course.id ?? 0);
@ -35,32 +71,46 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
LearnModuleViewModel viewModel, LearnModuleViewModel viewModel,
Widget? child, Widget? child,
) => ) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper(LearnModuleViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffoldContainer(viewModel), body: _buildScaffoldContainer(context: context, viewModel: viewModel),
); );
Widget _buildScaffoldContainer(LearnModuleViewModel viewModel) => Container( Widget _buildScaffoldContainer(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Container(
decoration: bgDecoration, decoration: bgDecoration,
child: _buildScaffold(viewModel), child: _buildScaffold(context: context, viewModel: viewModel),
); );
Widget _buildScaffold(LearnModuleViewModel viewModel) => Widget _buildScaffold(
SafeArea(child: _buildBody(viewModel)); {required BuildContext context,
required LearnModuleViewModel viewModel}) =>
SafeArea(child: _buildBody(context: context, viewModel: viewModel));
Widget _buildBody(LearnModuleViewModel viewModel) => Padding( Widget _buildBody(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel), child: _buildColumn(context: context, viewModel: viewModel),
); );
Widget _buildColumn(LearnModuleViewModel viewModel) => Column( Widget _buildColumn(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildModulesColumnWrapper(viewModel), _buildModulesColumnWrapper(context: context, viewModel: viewModel),
], ],
); );
@ -69,28 +119,41 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
showBackButton: true, showBackButton: true,
); );
Widget _buildModulesColumnWrapper(LearnModuleViewModel viewModel) => Widget _buildModulesColumnWrapper(
Expanded(child: _buildLevelsColumnScrollView(viewModel)); {required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Expanded(
child: _buildLevelsColumnScrollView(
context: context, viewModel: viewModel));
Widget _buildLevelsColumnScrollView(LearnModuleViewModel viewModel) => Widget _buildLevelsColumnScrollView(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildLevelsColumn(viewModel), child: _buildLevelsColumn(context: context, viewModel: viewModel),
); );
Widget _buildLevelsColumn(LearnModuleViewModel viewModel) => Column( Widget _buildLevelsColumn(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: _buildLevelsColumnChildren(viewModel), children:
_buildLevelsColumnChildren(context: context, viewModel: viewModel),
); );
List<Widget> _buildLevelsColumnChildren(LearnModuleViewModel viewModel) => [ List<Widget> _buildLevelsColumnChildren(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
[
verticalSpaceSmall, verticalSpaceSmall,
_buildTitle(), _buildTitle(),
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildOverallProgress(), _buildOverallProgress(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildListViewBuilder(viewModel) _buildListViewBuilder(context: context, viewModel: viewModel)
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -103,30 +166,41 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
style: style14P400, style: style14P400,
); );
Widget _buildOverallProgress() => OverallProgress( Widget _buildOverallProgress(LearnModuleViewModel viewModel) =>
OverallProgress(
indicatorBackgroundColor: kcWhite, indicatorBackgroundColor: kcWhite,
progress: course.access?.progressPercent ?? 0,
backgroundColor: kcPrimaryColor.withOpacity(0.1), backgroundColor: kcPrimaryColor.withOpacity(0.1),
progress: (viewModel.getUpdatedLearnCourse(course.id ?? 0) ?? course)
.access
?.progressPercent ??
0,
); );
Widget _buildListViewBuilder(LearnModuleViewModel viewModel) => Widget _buildListViewBuilder(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
viewModel.busy(StateObjects.learnModules) viewModel.busy(StateObjects.learnModules)
? _buildProgressIndicator() ? _buildProgressIndicator()
: _buildListView(viewModel); : _buildListView(context: context, viewModel: viewModel);
Widget _buildProgressIndicator() => const Center( Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor), child: CustomCircularProgressIndicator(color: kcPrimaryColor),
); );
Widget _buildListView(LearnModuleViewModel viewModel) => ListView.builder( Widget _buildListView(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.modules.length, itemCount: viewModel.modules.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index], module: viewModel.modules[index],
onPracticeTap: () async => await viewModel.navigateToLearnPractice( onPracticeTap: () async => await _onPractice(
id: viewModel.modules[index].id ?? 0, context: context,
module: viewModel.modules[index].name ?? ''), viewModel: viewModel,
module: viewModel.modules[index],
),
onModuleTap: () async => onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]), await viewModel.navigateToLearnLesson(viewModel.modules[index]),
), ),
@ -141,4 +215,12 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
module: module, module: module,
onModuleTap: onModuleTap, onModuleTap: onModuleTap,
onPracticeTap: onPracticeTap); onPracticeTap: onPracticeTap);
Widget _buildSheet(
{required PracticeReason practice,
required LearnModuleViewModel viewModel}) =>
FinishPracticeSheet(
practice: practice,
onTap: viewModel.pop,
);
} }

View File

@ -6,9 +6,11 @@ import 'package:yimaru_app/models/learn_module.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/learn_course.dart';
import '../../../services/learn_service.dart'; import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/helper_functions.dart';
class LearnModuleViewModel extends ReactiveViewModel { class LearnModuleViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
@ -22,6 +24,10 @@ class LearnModuleViewModel extends ReactiveViewModel {
List<ListenableServiceMixin> get listenableServices => [_learnService]; List<ListenableServiceMixin> get listenableServices => [_learnService];
// Learn module // Learn module
final Map<int, String> _refreshedIcons = {};
Map<int, String> get refreshedIcons => _refreshedIcons;
List<LearnModule> get _modules => _learnService.modules; List<LearnModule> get _modules => _learnService.modules;
List<LearnModule> get modules => _modules; List<LearnModule> get modules => _modules;
@ -29,8 +35,6 @@ class LearnModuleViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLesson(LearnModule module) async => Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module); await _navigationService.navigateToLearnLessonView(module: module);
@ -54,6 +58,32 @@ class LearnModuleViewModel extends ReactiveViewModel {
Future<void> _getLearnModules(int id) async { Future<void> _getLearnModules(int id) async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
await _learnService.getLearnModules(id); await _learnService.getLearnModules(id);
await refreshModuleImages(_modules);
}
}
// Get course
LearnCourse? getUpdatedLearnCourse(int id) {
return _learnService.getLearnCourseById(id);
}
//Refresh image
Future<void> refreshModuleImages(List<LearnModule> modules) async {
for (final module in modules) {
final icon = module.icon;
if (module.id == null || icon == null || icon.isEmpty) {
continue;
}
final String? refreshedUrl = await _learnService.refreshObject(icon);
if (refreshedUrl != null) {
_refreshedIcons[module.id!] = refreshedUrl;
} }
} }
} }
String getModuleImage(LearnModule module) =>
getReadableUrl(_refreshedIcons[module.id] ?? '') ?? '';
}

View File

@ -16,6 +16,7 @@ import '../../../services/audio_player_service.dart';
import '../../../services/learn_service.dart'; import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/helper_functions.dart';
class LearnPracticeViewModel extends ReactiveViewModel { class LearnPracticeViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
@ -94,10 +95,15 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Voice? get playing => _playing; Voice? get playing => _playing;
// Learn practices // Learn practices
List<LearnPractice> _practices = []; List<LearnPractice> _practices = [];
List<LearnPractice> get practices => _practices; List<LearnPractice> get practices => _practices;
final Map<int, String> _refreshedImages = {};
Map<int, String> get refreshedImages => _refreshedImages;
// Practice questions // Practice questions
List<LearnQuestion> _questions = []; List<LearnQuestion> _questions = [];
@ -260,6 +266,10 @@ class LearnPracticeViewModel extends ReactiveViewModel {
// Remote api call // Remote api call
// Refresh url
Future<String?> refreshUrl(String url) async =>
await _learnService.refreshObject(url);
// Learn practice // Learn practice
Future<void> getLearnPractices( Future<void> getLearnPractices(
{required int id, required LearnPractices practice}) async => {required int id, required LearnPractices practice}) async =>
@ -271,14 +281,14 @@ class LearnPracticeViewModel extends ReactiveViewModel {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
if (practice == LearnPractices.course) { if (practice == LearnPractices.course) {
_practices = await _apiService.getLearnCoursePractices(id); _practices = await _apiService.getLearnCoursePractices(id);
// await refreshPracticeImages(_practices);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else if (practice == LearnPractices.module) { } else if (practice == LearnPractices.module) {
_practices = await _apiService.getLearnModulePractices(id); _practices = await _apiService.getLearnModulePractices(id);
// await refreshPracticeImages(_practices);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else { } else {
_practices = await _apiService.getLearnLessonPractices(id); _practices = await _apiService.getLearnLessonPractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} }
} }
@ -296,6 +306,27 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Future<void> _completeLearnPractices() async { Future<void> _completeLearnPractices() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
await _apiService.completeLearnPractice(_practices.first.id ?? 0); await _apiService.completeLearnPractice(_practices.first.id ?? 0);
await _learnService.getLearnProgress();
}
}
//Refresh image
Future<void> refreshPracticeImages(List<LearnPractice> practices) async {
for (final practice in practices) {
final image = practice.storyImage;
if (practice.id == null || image == null || image.isEmpty) {
continue;
}
final String? refreshedUrl = await _learnService.refreshObject(image);
if (refreshedUrl != null) {
_refreshedImages[practice.id!] = refreshedUrl;
} }
} }
} }
String getPracticeImage(LearnPractice practice) =>
getReadableUrl(_refreshedImages[practice.id] ?? '') ?? '';
}

View File

@ -127,7 +127,8 @@ class InteractLearnPracticeScreen
showBackButton: true, showBackButton: true,
onPop: () async => onPop: () async =>
await _showSheet(context: context, viewModel: viewModel), await _showSheet(context: context, viewModel: viewModel),
title: '${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})'); title:
'${LocaleKeys.practice_speaking.tr()} ($index/${viewModel.questions.length})');
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
Column( Column(

View File

@ -15,9 +15,6 @@ class LearnPracticeAppreciationScreen
extends ViewModelWidget<LearnPracticeViewModel> { extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeAppreciationScreen({super.key}); const LearnPracticeAppreciationScreen({super.key});
Future<void> _reset(LearnPracticeViewModel viewModel) async =>
await viewModel.reset();
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording(); await viewModel.stopRecording();
viewModel.pop(); viewModel.pop();
@ -158,6 +155,5 @@ class LearnPracticeAppreciationScreen
onTap: () => viewModel.goTo(4), onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: LocaleKeys.view_results.tr(), text: LocaleKeys.view_results.tr(),
); );
} }

View File

@ -89,14 +89,12 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
program: viewModel.learnPrograms[index], program: viewModel.learnPrograms[index],
onTap: () async => await viewModel onTap: () async => await viewModel
.navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0), .navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0),
onLockTap: () async => await viewModel.navigateToLearnSubscription(),
), ),
); );
Widget _buildTile({ Widget _buildTile({
required LearnProgram program, required LearnProgram program,
required GestureTapCallback onTap, required GestureTapCallback onTap,
required GestureTapCallback onLockTap,
}) => }) =>
LearnProgramTile(onTap: onTap, program: program,onLockTap: onLockTap,); LearnProgramTile(onTap: onTap, program: program);
} }

View File

@ -39,9 +39,6 @@ class LearnProgramViewModel extends ReactiveViewModel {
Future<void> navigateToLearnCourse(int id) async => Future<void> navigateToLearnCourse(int id) async =>
_navigationService.navigateToLearnCourseView(id: id); _navigationService.navigateToLearnCourseView(id: id);
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
// Remote api call // Remote api call
// Learn programs // Learn programs

View File

@ -31,6 +31,10 @@ class LearnSubscriptionViewModel extends FormViewModel {
int get selectedIndex => _selectedIndex; int get selectedIndex => _selectedIndex;
LearnSubscription? _selectedSubscription;
LearnSubscription? get selectedSubscription => _selectedSubscription;
List<LearnSubscription> _subscriptions = []; List<LearnSubscription> _subscriptions = [];
List<LearnSubscription> get subscriptions => _subscriptions; List<LearnSubscription> get subscriptions => _subscriptions;
@ -40,6 +44,13 @@ class LearnSubscriptionViewModel extends FormViewModel {
_focusPhoneNumber = true; _focusPhoneNumber = true;
rebuildUi(); rebuildUi();
} }
//Learn subscriptions
void setSelectedPricing(int index) {
_selectedIndex = index;
_selectedSubscription = _subscriptions[index];
rebuildUi();
}
// In-app navigation // In-app navigation
@ -60,16 +71,11 @@ class LearnSubscriptionViewModel extends FormViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToArifPay(String phone) async { Future<void> navigateToArifPay(
{required String phone, required LearnSubscription? subscription}) async {
pop(); pop();
await _navigationService.navigateToArifPayView(phone: phone); await _navigationService.navigateToPaymentView(
} phone: phone, subscription: subscription ?? _subscriptions[0]);
//Learn subscriptions
void setSelectedPricing(int index) {
_selectedIndex = index;
rebuildUi();
} }
// Remote api call // Remote api call
@ -82,7 +88,6 @@ class LearnSubscriptionViewModel extends FormViewModel {
Future<void> _getLearnSubscriptions() async { Future<void> _getLearnSubscriptions() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
_subscriptions = await _apiService.getLearnSubscriptions(); _subscriptions = await _apiService.getLearnSubscriptions();
_subscriptions = _subscriptions + _subscriptions + _subscriptions;
} }
} }
} }

View File

@ -6,6 +6,7 @@ import 'package:yimaru_app/ui/widgets/phone_number_prefix.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/learn_subscription_card.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
import '../learn_subscription_view.form.dart'; import '../learn_subscription_view.form.dart';
@ -73,11 +74,15 @@ class LearnSubscriptionFormScreen
); );
List<Widget> _buildSheetChildren(LearnSubscriptionViewModel viewModel) => [ List<Widget> _buildSheetChildren(LearnSubscriptionViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceSmall,
_buildTitleWrapper(), _buildTitleWrapper(),
verticalSpaceTiny, verticalSpaceMedium,
_buildFirstCard(),
verticalSpaceMedium,
_buildSecondCard(),
verticalSpaceLarge,
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMassive, verticalSpaceMedium,
_buildPhoneNumberWrapper(viewModel), _buildPhoneNumberWrapper(viewModel),
if (viewModel.hasPhoneNumberValidationMessage && if (viewModel.hasPhoneNumberValidationMessage &&
viewModel.focusPhoneNumber) viewModel.focusPhoneNumber)
@ -87,10 +92,22 @@ class LearnSubscriptionFormScreen
_buildPhoneNumberValidatorWrapper(viewModel), _buildPhoneNumberValidatorWrapper(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
verticalSpaceMassive, verticalSpaceMedium,
_buildSecurePaymentWrapper() _buildSecurePaymentWrapper()
]; ];
Widget _buildFirstCard() => const LearnSubscriptionCard(
icon: Icons.school,
title: '180+ New Lessons',
subtitle: 'Access fresh, advanced content',
);
Widget _buildSecondCard() => const LearnSubscriptionCard(
icon: Icons.developer_board,
title: 'Mastery Through Practice',
subtitle: 'Practice All Lessons, Modules & Levels',
);
Widget _buildTitleWrapper() => Align( Widget _buildTitleWrapper() => Align(
alignment: Alignment.center, alignment: Alignment.center,
child: _buildTitle(), child: _buildTitle(),
@ -161,8 +178,9 @@ class LearnSubscriptionFormScreen
? kcPrimaryColor ? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
onTap: phoneNumberController.text.isNotEmpty onTap: phoneNumberController.text.isNotEmpty
? () async => ? () async => await viewModel.navigateToArifPay(
await viewModel.navigateToArifPay(phoneNumberController.text) phone: phoneNumberController.text,
subscription: viewModel.selectedSubscription)
: null, : null,
); );

View File

@ -7,6 +7,7 @@ import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/models/user.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/apple_auth_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart'; import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart'; import '../../../services/localization_service.dart';
@ -25,19 +26,23 @@ class LoginViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _appleAuthService = locator<AppleAuthService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_googleAuthService, _localizationService]; [_googleAuthService, _appleAuthService, _localizationService];
// Google user // Google user
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser; GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
GoogleSignInAccount? get googleUser => _googleUser; GoogleSignInAccount? get googleUser => _googleUser;
bool get isAppleSignInAvailable => _appleAuthService.isSupported;
// Languages // Languages
Map<String, dynamic> get _selectedLanguage => Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage; _localizationService.selectedLanguage;
@ -235,6 +240,50 @@ class LoginViewModel extends ReactiveViewModel
Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(), Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(),
busyObject: StateObjects.loginWithGoogle); busyObject: StateObjects.loginWithGoogle);
// Sign-in with Apple
Future<void> _appleAuth() async {
if (await _statusChecker.checkConnection()) {
await _appleAuthService.appleAuth();
final credential = _appleAuthService.appleCredential;
final identityToken = credential?.identityToken;
if (identityToken == null || identityToken.isEmpty) {
showErrorToast('Apple login failed. Please try again.');
return;
}
Map<String, dynamic> data = {
'id_token': identityToken,
'email': credential?.email,
'first_name': credential?.givenName,
'last_name': credential?.familyName,
};
data.removeWhere((_, value) => value == null || value == '');
Map<String, dynamic> response = await _apiService.appleAuth(data);
if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User;
Map<String, dynamic> data = {
'userId': user.userId,
'accessToken': user.accessToken,
'refreshToken': user.refreshToken
};
await _authenticationService.saveUserCredential(data);
clearUserData();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
}
}
}
Future<void> signInWithApple() async => await runBusyFuture(_appleAuth(),
busyObject: StateObjects.loginWithApple);
// Login with phone // Login with phone
Future<void> loginWithPhoneNumber() async => Future<void> loginWithPhoneNumber() async =>
await runBusyFuture(_loginWithPhoneNumber(), await runBusyFuture(_loginWithPhoneNumber(),

View File

@ -92,8 +92,10 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -66,7 +66,8 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
Stack(children: [ Stack(children: [
_buildScaffold(context: context, viewModel: viewModel), _buildScaffold(context: context, viewModel: viewModel),
_buildLoginWithEmailState(viewModel), _buildLoginWithEmailState(viewModel),
_buildLoginWithGoogleState(viewModel) _buildLoginWithGoogleState(viewModel),
_buildLoginWithAppleState(viewModel)
]); ]);
Widget _buildScaffold( Widget _buildScaffold(
@ -88,8 +89,10 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar( Widget _buildAppBar(LoginViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(
@ -230,6 +233,8 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
List<Widget> _buildLowerColumnChildren(LoginViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(LoginViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
_buildLoginWithGoogleButton(viewModel), _buildLoginWithGoogleButton(viewModel),
if (viewModel.isAppleSignInAvailable)
_buildLoginWithAppleButton(viewModel),
_buildOptionTextDivider(), _buildOptionTextDivider(),
_buildLoginWithPhoneButton(viewModel), _buildLoginWithPhoneButton(viewModel),
verticalSpaceMedium verticalSpaceMedium
@ -263,6 +268,18 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
onTap: () async => await viewModel.signInWithGoogle(), onTap: () async => await viewModel.signInWithGoogle(),
); );
Widget _buildLoginWithAppleButton(LoginViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
text: LocaleKeys.login_with_apple.tr(),
leadingIcon: Icons.apple,
onTap: () async => await viewModel.signInWithApple(),
);
Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildLoginWithPhoneButton(LoginViewModel viewModel) => Widget _buildLoginWithPhoneButton(LoginViewModel viewModel) =>
@ -285,4 +302,9 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
viewModel.busy(StateObjects.loginWithGoogle) viewModel.busy(StateObjects.loginWithGoogle)
? const PageLoadingIndicator() ? const PageLoadingIndicator()
: Container(); : Container();
Widget _buildLoginWithAppleState(LoginViewModel viewModel) =>
viewModel.busy(StateObjects.loginWithApple)
? const PageLoadingIndicator()
: Container();
} }

View File

@ -85,8 +85,10 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => viewModel.goTo(0), onPop: () => viewModel.goTo(0),
language: viewModel.selectedLanguage['code'],
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
language: viewModel.selectedLanguage['code'] == 'am'
? 'አማ'
: viewModel.selectedLanguage['code'],
); );
Widget _buildExpandedBody( Widget _buildExpandedBody(

View File

@ -8,8 +8,8 @@ import 'package:yimaru_app/ui/views/onboarding/screens/country_region_form_scree
import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/educational_background_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/full_name_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/full_name_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/gender_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/gender_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/learning_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/language_goal_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/occupation_form_screen.dart';
import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart'; import 'package:yimaru_app/ui/views/onboarding/screens/topic_form_screen.dart';
@ -18,12 +18,9 @@ import 'onboarding_viewmodel.dart';
import 'onboarding_view.form.dart'; import 'onboarding_view.form.dart';
@FormView(fields: [ @FormView(fields: [
FormTextField(name: 'topic', validator: FormValidator.validateForm),
FormTextField( FormTextField(
name: 'fullName', validator: FormValidator.validateFullNameForm), name: 'fullName', validator: FormValidator.validateFullNameForm),
FormTextField(name: 'region', validator: FormValidator.validateForm), FormTextField(name: 'region', validator: FormValidator.validateForm),
FormTextField(name: 'challenge', validator: FormValidator.validateForm),
FormTextField(name: 'languageGoal', validator: FormValidator.validateForm),
]) ])
class OnboardingView extends StackedView<OnboardingViewModel> class OnboardingView extends StackedView<OnboardingViewModel>
with $OnboardingView { with $OnboardingView {
@ -34,11 +31,8 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} }
void _initClearData() { void _initClearData() {
topicController.clear();
regionController.clear(); regionController.clear();
fullNameController.clear(); fullNameController.clear();
challengeController.clear();
languageGoalController.clear();
} }
void _clearDataOnNavigation(OnboardingViewModel viewModel) { void _clearDataOnNavigation(OnboardingViewModel viewModel) {
@ -54,17 +48,15 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} else if (viewModel.currentPage == 4) { } else if (viewModel.currentPage == 4) {
viewModel.resetOccupationFormScreen(); viewModel.resetOccupationFormScreen();
} else if (viewModel.currentPage == 5) { } else if (viewModel.currentPage == 5) {
regionController.clear();
viewModel.resetCountryRegionFormScreen(); viewModel.resetCountryRegionFormScreen();
} else if (viewModel.currentPage == 6) { } else if (viewModel.currentPage == 6) {
viewModel.resetLearningGoalFormScreen(); viewModel.resetLearningGoalFormScreen();
} else if (viewModel.currentPage == 7) { } else if (viewModel.currentPage == 7) {
languageGoalController.clear();
viewModel.resetLanguageGoalFormScreen(); viewModel.resetLanguageGoalFormScreen();
} else if (viewModel.currentPage == 8) { } else if (viewModel.currentPage == 8) {
challengeController.clear();
viewModel.resetChallengeFormScreen(); viewModel.resetChallengeFormScreen();
} else if (viewModel.currentPage == 9) { } else if (viewModel.currentPage == 9) {
topicController.clear();
viewModel.resetTopicFormScreen(); viewModel.resetTopicFormScreen();
} }
} }
@ -77,7 +69,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
} }
@override @override
void onViewModelReady(OnboardingViewModel viewModel) { void onViewModelReady(OnboardingViewModel viewModel) async {
_initClearData(); _initClearData();
_initUserData(viewModel); _initUserData(viewModel);
syncFormWithViewModel(viewModel); syncFormWithViewModel(viewModel);
@ -134,17 +126,14 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOccupationForm() => const OccupationFormScreen(); Widget _buildOccupationForm() => const OccupationFormScreen();
Widget _buildCountryRegionForm() => CountryRegionFormScreen( Widget _buildCountryRegionForm() =>
regionController: regionController, CountryRegionFormScreen(regionController: regionController);
);
Widget _buildLearningGoalForm() => const LearningGoalFormScreen(); Widget _buildLearningGoalForm() => const LanguageGoalFormScreens();
Widget _buildLanguageGoalForm() => Widget _buildLanguageGoalForm() => const LearningGoalFormScreen();
LanguageGoalFormScreen(languageGoalController: languageGoalController);
Widget _buildChallengeForm() => Widget _buildChallengeForm() => const ChallengeFormScreen();
ChallengeFormScreen(challengeController: challengeController);
Widget _buildTopicForm() => TopicFormScreen(topicController: topicController); Widget _buildTopicForm() => const TopicFormScreen();
} }

View File

@ -13,11 +13,8 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
const bool _autoTextFieldValidation = true; const bool _autoTextFieldValidation = true;
const String TopicValueKey = 'topic';
const String FullNameValueKey = 'fullName'; const String FullNameValueKey = 'fullName';
const String RegionValueKey = 'region'; const String RegionValueKey = 'region';
const String ChallengeValueKey = 'challenge';
const String LanguageGoalValueKey = 'languageGoal';
final Map<String, TextEditingController> _OnboardingViewTextEditingControllers = final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
{}; {};
@ -25,31 +22,18 @@ final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
final Map<String, FocusNode> _OnboardingViewFocusNodes = {}; final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = { final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
TopicValueKey: FormValidator.validateForm,
FullNameValueKey: FormValidator.validateFullNameForm, FullNameValueKey: FormValidator.validateFullNameForm,
RegionValueKey: FormValidator.validateForm, RegionValueKey: FormValidator.validateForm,
ChallengeValueKey: FormValidator.validateForm,
LanguageGoalValueKey: FormValidator.validateForm,
}; };
mixin $OnboardingView { mixin $OnboardingView {
TextEditingController get topicController =>
_getFormTextEditingController(TopicValueKey);
TextEditingController get fullNameController => TextEditingController get fullNameController =>
_getFormTextEditingController(FullNameValueKey); _getFormTextEditingController(FullNameValueKey);
TextEditingController get regionController => TextEditingController get regionController =>
_getFormTextEditingController(RegionValueKey); _getFormTextEditingController(RegionValueKey);
TextEditingController get challengeController =>
_getFormTextEditingController(ChallengeValueKey);
TextEditingController get languageGoalController =>
_getFormTextEditingController(LanguageGoalValueKey);
FocusNode get topicFocusNode => _getFormFocusNode(TopicValueKey);
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey); FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey); FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
FocusNode get languageGoalFocusNode =>
_getFormFocusNode(LanguageGoalValueKey);
TextEditingController _getFormTextEditingController( TextEditingController _getFormTextEditingController(
String key, { String key, {
@ -75,11 +59,8 @@ mixin $OnboardingView {
/// Registers a listener on every generated controller that calls [model.setData()] /// Registers a listener on every generated controller that calls [model.setData()]
/// with the latest textController values /// with the latest textController values
void syncFormWithViewModel(FormStateHelper model) { void syncFormWithViewModel(FormStateHelper model) {
topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -91,11 +72,8 @@ mixin $OnboardingView {
'This feature was deprecated after 3.1.0.', 'This feature was deprecated after 3.1.0.',
) )
void listenToFormUpdated(FormViewModel model) { void listenToFormUpdated(FormViewModel model) {
topicController.addListener(() => _updateFormData(model));
fullNameController.addListener(() => _updateFormData(model)); fullNameController.addListener(() => _updateFormData(model));
regionController.addListener(() => _updateFormData(model)); regionController.addListener(() => _updateFormData(model));
challengeController.addListener(() => _updateFormData(model));
languageGoalController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation); _updateFormData(model, forceValidate: _autoTextFieldValidation);
} }
@ -105,11 +83,8 @@ mixin $OnboardingView {
model.setData( model.setData(
model.formValueMap model.formValueMap
..addAll({ ..addAll({
TopicValueKey: topicController.text,
FullNameValueKey: fullNameController.text, FullNameValueKey: fullNameController.text,
RegionValueKey: regionController.text, RegionValueKey: regionController.text,
ChallengeValueKey: challengeController.text,
LanguageGoalValueKey: languageGoalController.text,
}), }),
); );
@ -151,22 +126,8 @@ extension ValueProperties on FormStateHelper {
return !hasAnyValidationMessage; return !hasAnyValidationMessage;
} }
String? get topicValue => this.formValueMap[TopicValueKey] as String?;
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?; String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
String? get regionValue => this.formValueMap[RegionValueKey] as String?; String? get regionValue => this.formValueMap[RegionValueKey] as String?;
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
String? get languageGoalValue =>
this.formValueMap[LanguageGoalValueKey] as String?;
set topicValue(String? value) {
this.setData(
this.formValueMap..addAll({TopicValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(TopicValueKey)) {
_OnboardingViewTextEditingControllers[TopicValueKey]?.text = value ?? '';
}
}
set fullNameValue(String? value) { set fullNameValue(String? value) {
this.setData( this.setData(
@ -189,97 +150,41 @@ extension ValueProperties on FormStateHelper {
} }
} }
set challengeValue(String? value) {
this.setData(
this.formValueMap..addAll({ChallengeValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(ChallengeValueKey)) {
_OnboardingViewTextEditingControllers[ChallengeValueKey]?.text =
value ?? '';
}
}
set languageGoalValue(String? value) {
this.setData(
this.formValueMap..addAll({LanguageGoalValueKey: value}),
);
if (_OnboardingViewTextEditingControllers.containsKey(
LanguageGoalValueKey)) {
_OnboardingViewTextEditingControllers[LanguageGoalValueKey]?.text =
value ?? '';
}
}
bool get hasTopic =>
this.formValueMap.containsKey(TopicValueKey) &&
(topicValue?.isNotEmpty ?? false);
bool get hasFullName => bool get hasFullName =>
this.formValueMap.containsKey(FullNameValueKey) && this.formValueMap.containsKey(FullNameValueKey) &&
(fullNameValue?.isNotEmpty ?? false); (fullNameValue?.isNotEmpty ?? false);
bool get hasRegion => bool get hasRegion =>
this.formValueMap.containsKey(RegionValueKey) && this.formValueMap.containsKey(RegionValueKey) &&
(regionValue?.isNotEmpty ?? false); (regionValue?.isNotEmpty ?? false);
bool get hasChallenge =>
this.formValueMap.containsKey(ChallengeValueKey) &&
(challengeValue?.isNotEmpty ?? false);
bool get hasLanguageGoal =>
this.formValueMap.containsKey(LanguageGoalValueKey) &&
(languageGoalValue?.isNotEmpty ?? false);
bool get hasTopicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
bool get hasFullNameValidationMessage => bool get hasFullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
bool get hasRegionValidationMessage => bool get hasRegionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false; this.fieldsValidationMessages[RegionValueKey]?.isNotEmpty ?? false;
bool get hasChallengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey]?.isNotEmpty ?? false;
bool get hasLanguageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey]?.isNotEmpty ?? false;
String? get topicValidationMessage =>
this.fieldsValidationMessages[TopicValueKey];
String? get fullNameValidationMessage => String? get fullNameValidationMessage =>
this.fieldsValidationMessages[FullNameValueKey]; this.fieldsValidationMessages[FullNameValueKey];
String? get regionValidationMessage => String? get regionValidationMessage =>
this.fieldsValidationMessages[RegionValueKey]; this.fieldsValidationMessages[RegionValueKey];
String? get challengeValidationMessage =>
this.fieldsValidationMessages[ChallengeValueKey];
String? get languageGoalValidationMessage =>
this.fieldsValidationMessages[LanguageGoalValueKey];
} }
extension Methods on FormStateHelper { extension Methods on FormStateHelper {
void setTopicValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[TopicValueKey] = validationMessage;
void setFullNameValidationMessage(String? validationMessage) => void setFullNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FullNameValueKey] = validationMessage; this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
void setRegionValidationMessage(String? validationMessage) => void setRegionValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[RegionValueKey] = validationMessage; this.fieldsValidationMessages[RegionValueKey] = validationMessage;
void setChallengeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
void setLanguageGoalValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage;
/// Clears text input fields on the Form /// Clears text input fields on the Form
void clearForm() { void clearForm() {
topicValue = '';
fullNameValue = ''; fullNameValue = '';
regionValue = ''; regionValue = '';
challengeValue = '';
languageGoalValue = '';
} }
/// Validates text input fields on the Form /// Validates text input fields on the Form
void validateForm() { void validateForm() {
this.setValidationMessages({ this.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey), RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
}); });
} }
} }
@ -299,9 +204,6 @@ String? getValidationMessage(String key) {
/// Updates the fieldsValidationMessages on the FormViewModel /// Updates the fieldsValidationMessages on the FormViewModel
void updateValidationData(FormStateHelper model) => void updateValidationData(FormStateHelper model) =>
model.setValidationMessages({ model.setValidationMessages({
TopicValueKey: getValidationMessage(TopicValueKey),
FullNameValueKey: getValidationMessage(FullNameValueKey), FullNameValueKey: getValidationMessage(FullNameValueKey),
RegionValueKey: getValidationMessage(RegionValueKey), RegionValueKey: getValidationMessage(RegionValueKey),
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
LanguageGoalValueKey: getValidationMessage(LanguageGoalValueKey),
}); });

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