Compare commits

..

25 Commits
ios ... main

Author SHA1 Message Date
b2c54487e8 Merge branch 'release/0.1.41'
-feat(course): Add duolingo screen for closed testing.
2026-06-05 11:45:24 +03:00
69e2047122 feat(course): Add duolingo screen for closed testing. 2026-06-05 11:44:39 +03:00
c7d0539cc0 Merge tag '0.1.40' into develop
-fix(learn_practice): Fix module and course practice
2026-06-04 16:50:10 +03:00
4698ddd5fb Merge branch 'release/0.1.40'
-fix(learn_practice): Fix module and course practice
2026-06-04 16:49:52 +03:00
37473951c4 fix(learn_practice): Fix module and course practice 2026-06-04 16:49:04 +03:00
42b106495a Merge tag '0.1.39' into develop
-fix(learn_practice): Fix module and course practice
2026-06-04 16:45:08 +03:00
faae4a6b66 Merge branch 'release/0.1.39'
-fix(learn_practice): Fix module and course practice
2026-06-04 16:44:50 +03:00
1664f83333 fix(learn_practice): Fix module and course practice 2026-06-04 16:43:59 +03:00
6635f64c27 Merge tag '0.1.38' into develop
-fix(learn_practice): Fix practice handling logic
2026-06-04 16:22:27 +03:00
18cce5658d Merge branch 'release/0.1.38'
-fix(learn_practice): Fix practice handling logic
2026-06-04 16:22:11 +03:00
788e591d83 fix(learn_practice): Fix practice handling logic 2026-06-04 16:21:21 +03:00
04381714db fix(learn_practice): Fix practice handling logic 2026-06-04 16:21:02 +03:00
517c232891 Merge tag '0.1.37' into develop
-fix(landing): Change landing image pictures for quality improvement
2026-06-04 13:12:14 +03:00
73d027f11c Merge branch 'release/0.1.37'
-fix(landing): Change landing image pictures for quality improvement
2026-06-04 13:11:58 +03:00
4fdda31eba fix(landing): Change landing image pictures for quality improvement 2026-06-04 13:11:14 +03:00
876c7a91e3 Merge tag '0.1.36' into develop
-fix(landing): Change landing image pictures for quality improvement.
2026-06-04 12:49:19 +03:00
0824019612 Merge branch 'release/0.1.36'
-fix(landing): Change landing image pictures for quality improvement
2026-06-04 12:48:49 +03:00
da7fac2b12 fix(landing): Change landing image pictures for quality improvement 2026-06-04 12:47:43 +03:00
0a20c639f0 Merge tag '0.1.35' into develop
-fix(subscription): Subscription logic updated
2026-06-03 04:02:06 +03:00
0936c04f3b Merge branch 'release/0.1.35'
-fix(subscription): Subscription logic updated
2026-06-03 04:01:47 +03:00
11f5161956 fix(subscription): Subscription logic updated 2026-06-03 04:01:04 +03:00
ae1c12e665 Merge tag '0.1.34' into develop
-fix(subscription): Subscription logic updated
2026-06-03 03:48:20 +03:00
2554b80359 Merge branch 'release/0.1.34'
-fix(subscription): Subscription logic updated
2026-06-03 03:48:00 +03:00
54d9ef56a2 fix(subscription): Subscription logic updated 2026-06-03 03:47:11 +03:00
7a2a72a554 Merge tag '0.1.33' into develop
-fix(subscription): Subscription update issue fixe
2026-06-02 16:36:30 +03:00
101 changed files with 2768 additions and 2204 deletions

View File

@ -0,0 +1,24 @@
<svg width="524" height="173" viewBox="0 0 524 173" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_23_9019)">
<path d="M194.31 28.74L206.48 51L219.27 28.76H232.21L212.29 62.86V83H200.67V62.84L180.75 28.74H194.31Z" fill="#9E2891"/>
<path d="M247.79 32.69C247.809 33.51 247.661 34.3252 247.353 35.0855C247.045 35.8457 246.584 36.5347 246 37.11C245.412 37.6962 244.71 38.1564 243.938 38.4625C243.166 38.7685 242.34 38.914 241.51 38.89C240.677 38.907 239.85 38.7584 239.076 38.4528C238.302 38.1472 237.596 37.6908 237 37.11C236.41 36.5373 235.944 35.8494 235.631 35.0891C235.317 34.3287 235.164 33.5121 235.18 32.69C235.159 31.8731 235.31 31.0608 235.623 30.3062C235.937 29.5515 236.406 28.8714 237 28.31C237.604 27.735 238.317 27.2853 239.096 26.9867C239.875 26.6881 240.706 26.5465 241.54 26.57C242.362 26.5477 243.18 26.6902 243.946 26.989C244.712 27.2878 245.41 27.737 246 28.31C246.586 28.8755 247.048 29.5571 247.356 30.3113C247.665 31.0655 247.812 31.8756 247.79 32.69ZM246.93 43.15V83H236V43.15H246.93Z" fill="#9E2891"/>
<path d="M316.92 61.13V83.07H306V60.74C306 54.7 303.7 51.68 299.1 51.68C298.017 51.6409 296.939 51.8473 295.947 52.2837C294.954 52.7201 294.074 53.3752 293.37 54.2C291.819 56.1914 291.048 58.6803 291.2 61.2V83.07H280.26V60.74C280.26 54.7 277.927 51.68 273.26 51.68C272.176 51.6412 271.098 51.855 270.11 52.3045C269.123 52.754 268.253 53.4268 267.57 54.27C266.061 56.2815 265.304 58.7583 265.43 61.27V83.07H254.53V43.15H264.06L265.06 48.15C266.341 46.4745 267.981 45.1076 269.86 44.15C272.055 43.1452 274.447 42.643 276.86 42.68C282.54 42.68 286.494 44.9267 288.72 49.42C290.134 47.2728 292.099 45.5457 294.41 44.42C296.907 43.2298 299.645 42.6342 302.41 42.68C304.364 42.6151 306.308 42.9667 308.115 43.7116C309.922 44.4564 311.55 45.5774 312.89 47C315.57 49.8067 316.914 54.5167 316.92 61.13Z" fill="#9E2891"/>
<path d="M363 83H357.42C352.4 83 349.944 80.83 350.05 76.49C348.731 78.6384 346.895 80.4226 344.71 81.68C342.347 82.9271 339.701 83.5403 337.03 83.46C332.59 83.46 329.014 82.44 326.3 80.4C324.968 79.4019 323.9 78.0925 323.19 76.5864C322.481 75.0802 322.151 73.4231 322.23 71.76C322.153 69.8652 322.549 67.9809 323.382 66.2771C324.214 64.5733 325.458 63.1034 327 62C330.12 59.7 334.654 58.55 340.6 58.55H348.51V56.56C348.542 55.736 348.377 54.9164 348.029 54.1687C347.681 53.4211 347.161 52.7668 346.51 52.26C344.913 51.1085 342.966 50.5465 341 50.67C339.238 50.5873 337.492 51.0481 336 51.99C335.383 52.3861 334.857 52.9079 334.455 53.5213C334.054 54.1348 333.786 54.8261 333.67 55.55H323.19C323.3 53.6561 323.848 51.8134 324.79 50.1668C325.732 48.5201 327.043 47.1144 328.62 46.06C331.874 43.8133 336.187 42.69 341.56 42.69C347.187 42.69 351.54 43.93 354.62 46.41C357.7 48.89 359.237 52.48 359.23 57.18V71.18C359.19 71.5381 359.224 71.9007 359.331 72.2449C359.438 72.5891 359.614 72.9075 359.85 73.18C360.408 73.5936 361.098 73.7892 361.79 73.73H363V83ZM340.47 65.78C338.602 65.6742 336.747 66.1493 335.16 67.14C334.544 67.5708 334.048 68.1509 333.717 68.8261C333.386 69.5012 333.233 70.2492 333.27 71C333.247 71.6411 333.379 72.2783 333.654 72.8578C333.929 73.4374 334.339 73.9423 334.85 74.33C336.106 75.2174 337.625 75.6544 339.16 75.57C340.39 75.6344 341.62 75.4517 342.777 75.0326C343.935 74.6135 344.997 73.9666 345.9 73.13C346.741 72.2633 347.401 71.2376 347.84 70.1129C348.28 68.9882 348.49 67.7872 348.46 66.58V65.8L340.47 65.78Z" fill="#9E2891"/>
<path d="M391.54 53.07H387.2C384.1 53.07 381.827 54 380.38 55.86C378.827 58.0793 378.063 60.7553 378.21 63.46V83H367.29V43.15H377.21L378.21 49.15C379.277 47.3096 380.799 45.7739 382.63 44.69C384.812 43.5902 387.239 43.0702 389.68 43.18H391.54V53.07Z" fill="#9E2891"/>
<path d="M433.94 83H424.25L423.25 78.19C421.853 79.9282 420.065 81.3117 418.032 82.2275C415.999 83.1434 413.778 83.5657 411.55 83.46C409.459 83.5413 407.373 83.2027 405.415 82.4641C403.457 81.7256 401.667 80.6021 400.15 79.16C397.21 76.2867 395.74 71.4967 395.74 64.79V43.15H406.66V63.46C406.66 67.08 407.3 69.8 408.56 71.63C409.218 72.552 410.101 73.2899 411.125 73.7731C412.149 74.2564 413.28 74.4687 414.41 74.39C415.642 74.4479 416.869 74.1975 417.979 73.6614C419.09 73.1253 420.049 72.3205 420.77 71.32C422.257 69.28 423 66.4267 423 62.76V43.15H433.93L433.94 83Z" fill="#9E2891"/>
<path d="M200.9 89.43H214.85L234.54 143.68H222.06L217.87 131.82H197.26L193.07 143.68H181.07L200.9 89.43ZM214.7 122.43L207.49 101.82L200.36 122.43H214.7Z" fill="#9E2891"/>
<path d="M255.77 144.15C251.999 144.235 248.268 143.369 244.92 141.63C241.873 140.003 239.377 137.507 237.75 134.46C236.09 131.132 235.226 127.464 235.226 123.745C235.226 120.026 236.09 116.358 237.75 113.03C239.403 109.984 241.927 107.501 245 105.9C248.362 104.162 252.107 103.296 255.89 103.38C261.224 103.38 265.557 104.737 268.89 107.45C272.315 110.281 274.532 114.312 275.09 118.72H263.75C263.384 116.884 262.417 115.223 261 114C259.536 112.814 257.693 112.197 255.81 112.26C254.504 112.217 253.205 112.482 252.02 113.035C250.835 113.587 249.797 114.411 248.99 115.44C247.323 117.885 246.431 120.776 246.431 123.735C246.431 126.694 247.323 129.585 248.99 132.03C249.799 133.056 250.838 133.877 252.023 134.427C253.207 134.978 254.505 135.243 255.81 135.2C257.737 135.266 259.623 134.634 261.12 133.42C262.579 132.164 263.552 130.438 263.87 128.54H275.11C274.597 133.016 272.376 137.121 268.91 140C265.517 142.76 261.137 144.143 255.77 144.15Z" fill="#9E2891"/>
<path d="M320.17 143.68H314.59C309.583 143.68 307.13 141.51 307.23 137.17C305.907 139.319 304.068 141.103 301.88 142.36C299.521 143.609 296.878 144.226 294.21 144.15C289.76 144.15 286.21 143.15 283.47 141.08C282.137 140.082 281.069 138.773 280.36 137.266C279.65 135.76 279.321 134.103 279.4 132.44C279.31 130.546 279.691 128.659 280.51 126.949C281.328 125.239 282.558 123.758 284.09 122.64C287.223 120.34 291.76 119.19 297.7 119.19H305.6V117.25C305.632 116.426 305.467 115.606 305.119 114.858C304.772 114.11 304.251 113.456 303.6 112.95C302.059 111.957 300.275 111.407 298.442 111.359C296.61 111.312 294.8 111.768 293.21 112.68C292.595 113.078 292.071 113.6 291.671 114.213C291.271 114.827 291.005 115.517 290.89 116.24H280.33C280.442 114.347 280.99 112.505 281.932 110.858C282.874 109.212 284.184 107.806 285.76 106.75C289.013 104.503 293.326 103.38 298.7 103.38C304.34 103.38 308.693 104.62 311.76 107.1C314.826 109.58 316.363 113.17 316.37 117.87V131.87C316.332 132.228 316.367 132.59 316.474 132.934C316.58 133.278 316.756 133.596 316.99 133.87C317.552 134.274 318.239 134.466 318.93 134.41H320.17V143.68ZM297.62 126.47C295.751 126.36 293.895 126.836 292.31 127.83C291.694 128.257 291.197 128.833 290.865 129.505C290.532 130.177 290.376 130.921 290.41 131.67C290.388 132.312 290.521 132.95 290.798 133.529C291.074 134.109 291.487 134.613 292 135C293.252 135.887 294.767 136.324 296.3 136.24C297.523 136.301 298.746 136.117 299.897 135.698C301.047 135.279 302.102 134.634 303 133.8C303.844 132.936 304.506 131.911 304.946 130.785C305.386 129.66 305.594 128.458 305.56 127.25V126.47H297.62Z" fill="#9E2891"/>
<path d="M364.27 143.68H354.73L353.65 138.18C352.225 140.111 350.352 141.666 348.191 142.712C346.031 143.757 343.649 144.261 341.25 144.18C337.925 144.23 334.65 143.358 331.79 141.66C328.968 139.944 326.695 137.456 325.24 134.49C323.61 131.134 322.802 127.44 322.88 123.71C322.795 120.002 323.604 116.329 325.24 113C326.711 110.043 328.998 107.569 331.83 105.87C334.706 104.174 337.992 103.302 341.33 103.35C346.49 103.35 350.49 105.157 353.33 108.77V89.43H364.26L364.27 143.68ZM353.5 123.84C353.645 120.825 352.708 117.857 350.86 115.47C350.01 114.432 348.931 113.605 347.708 113.054C346.485 112.502 345.151 112.241 343.81 112.29C342.464 112.241 341.124 112.503 339.895 113.054C338.665 113.605 337.579 114.432 336.72 115.47C334.978 117.885 334.041 120.787 334.041 123.765C334.041 126.743 334.978 129.645 336.72 132.06C337.581 133.095 338.668 133.919 339.897 134.468C341.126 135.018 342.465 135.278 343.81 135.23C345.147 135.283 346.479 135.027 347.701 134.482C348.924 133.938 350.005 133.119 350.86 132.09C352.705 129.748 353.642 126.818 353.5 123.84Z" fill="#9E2891"/>
<path d="M379.88 105.9C383.105 104.169 386.722 103.301 390.38 103.38C394.058 103.311 397.698 104.12 401 105.74C403.986 107.237 406.486 109.55 408.21 112.41C410.559 116.613 411.392 121.496 410.57 126.24H381.7V126.55C381.805 129.079 382.792 131.492 384.49 133.37C385.356 134.219 386.389 134.878 387.524 135.305C388.658 135.732 389.87 135.917 391.08 135.85C393.027 135.923 394.952 135.426 396.62 134.42C398.103 133.446 399.147 131.932 399.53 130.2H410.3C409.913 132.787 408.881 135.236 407.3 137.32C405.634 139.494 403.446 141.214 400.94 142.32C398.09 143.585 394.998 144.21 391.88 144.15C387.901 144.251 383.955 143.401 380.37 141.67C377.234 140.103 374.642 137.629 372.93 134.57C371.14 131.282 370.243 127.582 370.33 123.84C370.23 120.073 371.09 116.342 372.83 113C374.447 110.003 376.895 107.538 379.88 105.9ZM397 113.49C395.244 112.124 393.064 111.416 390.84 111.49C388.664 111.409 386.532 112.119 384.84 113.49C383.196 114.894 382.157 116.879 381.94 119.03H400C399.746 116.868 398.672 114.885 397 113.49Z" fill="#9E2891"/>
<path d="M479.28 121.82V143.76H468.35V121.44C468.35 115.393 466.05 112.37 461.45 112.37C460.366 112.329 459.288 112.535 458.295 112.972C457.303 113.408 456.422 114.064 455.72 114.89C454.167 116.881 453.392 119.369 453.54 121.89V143.75H442.62V121.44C442.62 115.393 440.286 112.37 435.62 112.37C434.534 112.33 433.453 112.543 432.464 112.992C431.475 113.442 430.604 114.115 429.92 114.96C428.413 116.973 427.659 119.449 427.79 121.96V143.74H416.89V103.84H426.42L427.42 108.84C428.7 107.164 430.341 105.798 432.22 104.84C434.415 103.835 436.806 103.333 439.22 103.37C444.9 103.37 448.85 105.617 451.07 110.11C452.49 107.965 454.458 106.238 456.77 105.11C459.266 103.92 462.004 103.324 464.77 103.37C466.717 103.299 468.657 103.642 470.462 104.376C472.267 105.111 473.895 106.22 475.24 107.63C477.926 110.477 479.273 115.207 479.28 121.82Z" fill="#9E2891"/>
<path d="M502.21 132.44L511.67 103.84H523.37L505.7 149.34C504.985 151.312 504.102 153.219 503.06 155.04C502.312 156.331 501.231 157.398 499.93 158.13C498.401 158.889 496.706 159.25 495 159.18H484.39V150H490.39C491.439 150.081 492.486 149.836 493.39 149.3C494.197 148.556 494.808 147.625 495.17 146.59L496.17 143.95L480.9 103.84H492.52L502.21 132.44Z" fill="#9E2891"/>
<path d="M121.07 26.53L98.76 39.42L76.03 52.54L53.26 39.39L53.24 39.38L30.96 26.52L31.41 25.76L53.24 13.15L53.26 13.13L76.02 0L98.76 13.12L120.6 25.74L121.07 26.53Z" fill="#9E2891"/>
<path d="M53.26 172.41H98.76V110.9L121.51 97.78L137.93 88.29L152.02 80.15L129.27 40.75L98.76 58.36L92.43 62.02L76 71.49L59.6 62.03L53.26 58.37L53.24 58.36L22.75 40.75L0 80.16L14.09 88.3L30.5 97.78L53.24 110.9L53.26 110.91V137.18V172.41Z" fill="#9E2891"/>
</g>
<defs>
<clipPath id="clip0_23_9019">
<rect width="523.37" height="172.42" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/images/landing_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

BIN
assets/images/landing_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

BIN
assets/images/landing_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

View File

@ -9,14 +9,12 @@
"cont": "ቀጥል",
"register": "ይመዝገቡ",
"login_with_google": "በጉግል ይግቡ",
"login_with_apple": "በአፕል ይግቡ",
"or": "ወይም",
"login_with_phone": "በስልክ ቁጥር ይግቡ",
"create_account": "አዲስ መለያ ይፍጠሩ",
"already_have_account": "መለያ አለዎት?",
"login": " ይግቡ ",
"register_with_google": "በጉግል ይመዝገቡ",
"register_with_apple": "በአፕል ይመዝገቡ",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"login_with_email": "በኢሜይል ይግቡ",
@ -195,7 +193,8 @@
"keep_momentum":"በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።",
"completed_practices": "የተጠናቀቁ ልምምዶች",
"total_practices": "ጠቅላላ ልምምዶች",
"progress_percentage": "የእድገት መቶኛ"
"progress_percentage": "የእድገት መቶኛ",
"notifications": "ማሳወቂያዎች"
}

View File

@ -9,14 +9,12 @@
"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",
@ -192,8 +190,9 @@
"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.",
"keep_momentum":"Great job! Keep the momentum.",
"completed_practices": "Completed Practices",
"total_practices": "Total Practices",
"progress_percentage": "Progress Percentage"
"progress_percentage": "Progress Percentage",
"notifications": "Notifications"
}

View File

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

View File

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

View File

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

View File

@ -1,46 +0,0 @@
# 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

View File

@ -1,28 +0,0 @@
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,13 +11,9 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
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 */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
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 */
/* Begin PBXContainerItemProxy section */
@ -46,19 +42,12 @@
/* Begin PBXFileReference section */
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
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; };
@ -66,27 +55,13 @@
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>"; };
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 */
/* Begin PBXFrameworksBuildPhase section */
932D3841F05A890DB5B188A4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
896DC9E666DF8098D827C010 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
99CE3BFD23F69C6D49568DE0 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -101,32 +76,9 @@
path = RunnerTests;
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 */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@ -142,8 +94,6 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
8096B886696A019BCD318B6B /* Pods */,
633D6DCBF46C9B68DCD511FE /* Frameworks */,
);
sourceTree = "<group>";
};
@ -163,8 +113,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
8E1B3E492C540A0B00F51C11 /* GoogleService-Info.plist */,
ACEEA7C32CFC6E9900D60211 /* Runner.entitlements */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@ -180,10 +128,8 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
94F71B9A2AE7A340918A2B71 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
932D3841F05A890DB5B188A4 /* Frameworks */,
);
buildRules = (
);
@ -199,24 +145,18 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
EDCDA15227D1D12483183F4E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
DE13979F911D29D4A95BCE2F /* [CP] Embed Pods Frameworks */,
E684A37538F596FB5432DE3F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@ -238,11 +178,6 @@
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
SystemCapabilities = {
com.apple.SignInWithApple = {
enabled = 1;
};
};
};
};
};
@ -255,9 +190,6 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@ -283,7 +215,6 @@
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
8E1B3E4A2C540A0B00F51C11 /* GoogleService-Info.plist in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -307,28 +238,6 @@
shellPath = /bin/sh;
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 */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -344,62 +253,6 @@
shellPath = /bin/sh;
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 */
/* Begin PBXSourcesBuildPhase section */
@ -493,7 +346,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -508,23 +361,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
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)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app;
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_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -533,14 +378,13 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 20E44080F42EAC6B045A6D89 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -551,14 +395,13 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 18336A33563E3B5B5B9974CC /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -567,14 +410,13 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A8C2A6C7D1D99F7BA12EAF94 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -630,7 +472,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -681,7 +523,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -698,23 +540,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
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)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app;
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_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -728,23 +562,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
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)";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K73ZX6D43Q;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.testapp;
PRODUCT_BUNDLE_IDENTIFIER = com.yimaru.lms.app;
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_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@ -785,20 +611,6 @@
defaultConfigurationName = Release;
};
/* 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 */;
}

View File

@ -1,167 +0,0 @@
{
"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,24 +5,6 @@
<BuildAction
parallelizeBuildables = "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>
<BuildActionEntry
buildForTesting = "YES"

View File

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

View File

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

View File

@ -1,36 +0,0 @@
<?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

@ -1,83 +1,51 @@
<?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>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Yimaru App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>yimaru_app</string>
<key>CFBundlePackageType</key>
<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>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Yimaru App</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>yimaru_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIStatusBarHidden</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>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -1,10 +0,0 @@
<?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

@ -35,7 +35,6 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart';
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart';
import 'package:yimaru_app/ui/views/failure/failure_view.dart';
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart';
import 'package:yimaru_app/services/notification_service.dart';
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart';
import 'package:yimaru_app/services/smart_auth_service.dart';
import 'package:yimaru_app/services/course_service.dart';
@ -56,9 +55,11 @@ import 'package:yimaru_app/services/localization_service.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/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';
import 'package:yimaru_app/ui/views/notification/notification_view.dart';
import 'package:yimaru_app/services/in_app_notification_service.dart';
import 'package:yimaru_app/services/push_notification_service.dart';
// @stacked-import
@StackedApp(
@ -98,6 +99,7 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
MaterialRoute(page: CourseModuleView),
MaterialRoute(page: LearnCourseView),
MaterialRoute(page: PaymentView),
MaterialRoute(page: NotificationView),
// @stacked-route
],
dependencies: [
@ -113,7 +115,6 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
LazySingleton(classType: ImagePickerService),
LazySingleton(classType: GoogleAuthService),
LazySingleton(classType: ImageDownloaderService),
LazySingleton(classType: NotificationService),
LazySingleton(classType: SmartAuthService),
LazySingleton(classType: CourseService),
LazySingleton(classType: AudioPlayerService),
@ -125,7 +126,8 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
LazySingleton(classType: LearnService),
LazySingleton(classType: LocalizationService),
LazySingleton(classType: OnboardingService),
LazySingleton(classType: AppleAuthService),
LazySingleton(classType: InAppNotificationService),
LazySingleton(classType: PushNotificationService),
// @stacked-service
],
bottomsheets: [

View File

@ -13,7 +13,6 @@ import 'package:stacked_services/src/navigation/navigation_service.dart';
import 'package:stacked_shared/stacked_shared.dart';
import '../services/api_service.dart';
import '../services/apple_auth_service.dart';
import '../services/audio_player_service.dart';
import '../services/authentication_service.dart';
import '../services/course_service.dart';
@ -21,13 +20,14 @@ import '../services/dio_service.dart';
import '../services/google_auth_service.dart';
import '../services/image_downloader_service.dart';
import '../services/image_picker_service.dart';
import '../services/in_app_notification_service.dart';
import '../services/in_app_update_service.dart';
import '../services/learn_service.dart';
import '../services/localization_service.dart';
import '../services/notification_service.dart';
import '../services/onboarding_service.dart';
import '../services/permission_handler_service.dart';
import '../services/phone_caller_service.dart';
import '../services/push_notification_service.dart';
import '../services/secure_storage_service.dart';
import '../services/smart_auth_service.dart';
import '../services/status_checker_service.dart';
@ -56,7 +56,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => ImagePickerService());
locator.registerLazySingleton(() => GoogleAuthService());
locator.registerLazySingleton(() => ImageDownloaderService());
locator.registerLazySingleton(() => NotificationService());
locator.registerLazySingleton(() => SmartAuthService());
locator.registerLazySingleton(() => CourseService());
locator.registerLazySingleton(() => AudioPlayerService());
@ -68,5 +67,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => LearnService());
locator.registerLazySingleton(() => LocalizationService());
locator.registerLazySingleton(() => OnboardingService());
locator.registerLazySingleton(() => AppleAuthService());
locator.registerLazySingleton(() => InAppNotificationService());
locator.registerLazySingleton(() => PushNotificationService());
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -7,8 +7,8 @@ import 'package:yimaru_app/app/app.dialogs.dart';
import 'package:yimaru_app/app/app.locator.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/services/notification_service.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:yimaru_app/services/push_notification_service.dart';
import 'package:yimaru_app/ui/common/translations/codegen_loader.g.dart';
import 'firebase_options.dart';
@ -17,7 +17,7 @@ Future<void> main() async {
await setupLocator();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await locator<NotificationService>().initialize();
await locator<PushNotificationService>().initialize();
await EasyLocalization.ensureInitialized();
setupDialogUi();
setupBottomSheetUi();

View File

@ -0,0 +1,57 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/notification_payload.dart';
part 'in_app_notification.g.dart';
@JsonSerializable()
class InAppNotification {
final String? id;
final String? type;
final String? level;
final String? image;
final NotificationPayload? payload;
@JsonKey(name: 'is_read')
final bool? isRead;
@JsonKey(name: 'reciever')
final String? receiver;
@JsonKey(name: 'recipient_id')
final int? recipientId;
@JsonKey(name: 'receiver_type')
final String? receiverType;
@JsonKey(name: 'error_severity')
final String? errorSeverity;
@JsonKey(name: 'delivery_status')
final String? deliveryStatus;
@JsonKey(name: 'delivery_channel')
final String? deliveryChannel;
const InAppNotification(
{this.id,
this.type,
this.image,
this.level,
this.isRead,
this.payload,
this.receiver,
this.recipientId,
this.receiverType,
this.errorSeverity,
this.deliveryStatus,
this.deliveryChannel});
factory InAppNotification.fromJson(Map<String, dynamic> json) =>
_$InAppNotificationFromJson(json);
Map<String, dynamic> toJson() => _$InAppNotificationToJson(this);
}

View File

@ -0,0 +1,42 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'in_app_notification.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
InAppNotification _$InAppNotificationFromJson(Map<String, dynamic> json) =>
InAppNotification(
id: json['id'] as String?,
type: json['type'] as String?,
image: json['image'] as String?,
level: json['level'] as String?,
isRead: json['is_read'] as bool?,
payload: json['payload'] == null
? null
: NotificationPayload.fromJson(
json['payload'] as Map<String, dynamic>),
receiver: json['reciever'] as String?,
recipientId: (json['recipient_id'] as num?)?.toInt(),
receiverType: json['receiver_type'] as String?,
errorSeverity: json['error_severity'] as String?,
deliveryStatus: json['delivery_status'] as String?,
deliveryChannel: json['delivery_channel'] as String?,
);
Map<String, dynamic> _$InAppNotificationToJson(InAppNotification instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
'level': instance.level,
'image': instance.image,
'payload': instance.payload,
'is_read': instance.isRead,
'reciever': instance.receiver,
'recipient_id': instance.recipientId,
'receiver_type': instance.receiverType,
'error_severity': instance.errorSeverity,
'delivery_status': instance.deliveryStatus,
'delivery_channel': instance.deliveryChannel,
};

View File

@ -0,0 +1,17 @@
import 'package:json_annotation/json_annotation.dart';
part 'notification_payload.g.dart';
@JsonSerializable()
class NotificationPayload {
final String? message;
final String? headline;
const NotificationPayload({this.message, this.headline});
factory NotificationPayload.fromJson(Map<String, dynamic> json) =>
_$NotificationPayloadFromJson(json);
Map<String, dynamic> toJson() => _$NotificationPayloadToJson(this);
}

View File

@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'notification_payload.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
NotificationPayload _$NotificationPayloadFromJson(Map<String, dynamic> json) =>
NotificationPayload(
message: json['message'] as String?,
headline: json['headline'] as String?,
);
Map<String, dynamic> _$NotificationPayloadToJson(
NotificationPayload instance) =>
<String, dynamic>{
'message': instance.message,
'headline': instance.headline,
};

View File

@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import 'package:yimaru_app/models/app_update.dart';
import 'package:yimaru_app/models/in_app_notification.dart';
import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart';
@ -112,34 +113,6 @@ 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
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
try {
@ -383,6 +356,55 @@ class ApiService {
}
}
// Mark notifications
Future<void> markNotificationsRead() async {
try {
await _service.dio.post(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl/$kMarkNotificationRead');
} catch (e) {
return;
}
}
// Get unread notifications
Future<int> getUnreadNotifications() async {
try {
final Response response = await _service.dio.get(
'$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl/$kUnreadUrl');
if (response.statusCode == 200) {
return response.data['unread'];
}
return 0;
} catch (e) {
return 0;
}
}
// Get notifications
Future<List<InAppNotification>> getAllNotifications() async {
try {
List<InAppNotification> notifications = [];
final Response response = await _service.dio
.get('$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kNotificationsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['notifications'] as List;
notifications = decodedData.map(
(e) {
return InAppNotification.fromJson(e);
},
).toList();
return notifications;
}
return [];
} catch (e) {
return [];
}
}
// Get assessment question sets
Future<List<Assessment>> getAssessments() async {
try {

View File

@ -1,36 +0,0 @@
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

@ -23,7 +23,7 @@ class AuthenticationService with ListenableServiceMixin {
// Initialization
AuthenticationService() {
listenToReactiveValues([_user,_state, _localizationService]);
listenToReactiveValues([_user, _state, _localizationService]);
}
// Logout state
@ -31,7 +31,6 @@ class AuthenticationService with ListenableServiceMixin {
StateObjects get state => _state;
// Check user logged in
Future<bool> userLoggedIn() async {
if (await _secureService.getString('userId') != null) {
@ -202,7 +201,6 @@ class AuthenticationService with ListenableServiceMixin {
await setFirstTimeInstall(firstTimeInstall);
await _secureService.setString('language', language);
_state = StateObjects.none;
notifyListeners();
}

View File

@ -29,7 +29,6 @@ class GoogleAuthService with ListenableServiceMixin {
_googleUser ??=
await _signIn.authenticate(scopeHint: ['email', 'profile']);
});
notifyListeners();
} catch (e) {

View File

@ -0,0 +1,44 @@
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/in_app_notification.dart';
import '../app/app.locator.dart';
import 'api_service.dart';
class InAppNotificationService with ListenableServiceMixin {
// Dependency injection
final _apiService = locator<ApiService>();
// Initialization
learnService() {
listenToReactiveValues([_unreadCount, _notifications]);
}
// Unread count
int _unreadCount = 0;
int get unreadCount => _unreadCount;
// Notifications
List<InAppNotification> _notifications = [];
List<InAppNotification> get notifications => _notifications;
// Unread notifications
Future<void> markNotificationRead() async {
await _apiService.markNotificationsRead();
_unreadCount = await _apiService.getUnreadNotifications();
notifyListeners();
}
// All notifications
Future<void> getAllNotifications() async {
_notifications = await _apiService.getAllNotifications();
notifyListeners();
}
// Unread notifications
Future<void> getUnreadNotifications() async {
_unreadCount = await _apiService.getUnreadNotifications();
notifyListeners();
}
}

View File

@ -58,8 +58,6 @@ class LearnService with ListenableServiceMixin {
List<LearnLesson> get lessons => _lessons;
// Learn programs
Future<String?> refreshObject(String url) async {
Map<String, dynamic> data = {'reference': url};

View File

@ -1,138 +0,0 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:yimaru_app/app/app.locator.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await locator<NotificationService>().setupFlutterNotifications();
await locator<NotificationService>().showNotification(message);
}
class NotificationService {
final _messaging = FirebaseMessaging.instance;
bool _isFlutterLocalNotificationInitialized = false;
final _localNotifications = FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
// Initialize FCM token
await updateFCMToken();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Request permission
await _requestPermission();
// setup message handle
await _setupMessageHandler();
// Subscribe to all devices
subscribeToTopic('yimaru');
}
Future<void> _requestPermission() async {
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
carPlay: false,
provisional: false,
announcement: false,
criticalAlert: false);
}
Future<void> setupFlutterNotifications() async {
if (_isFlutterLocalNotificationInitialized) {
return;
}
// Android setup
const channel = AndroidNotificationChannel(
'yimaru', // id
'Yimaru', // title
importance: Importance.high,
);
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
const initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// IOS setup
const initializationSettingsDarwin = DarwinInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin);
// Flutter notification setup
await _localNotifications.initialize(
settings: initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
},
);
_isFlutterLocalNotificationInitialized = true;
}
Future<void> showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
await _localNotifications.show(
id: notification.hashCode,
title: notification.title,
body: notification.body,
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails('yimaru', 'Yimaru',
enableVibration: true,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
importance: Importance.high),
iOS: DarwinNotificationDetails(
presentAlert: true, presentBadge: true, presentSound: true)),
);
}
}
Future<void> _setupMessageHandler() async {
// Foreground message
FirebaseMessaging.onMessage
.listen((RemoteMessage message) => showNotification(message));
// Background message
FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage);
// Opened app
final initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
_handleBackgroundMessage(initialMessage);
}
}
void _handleBackgroundMessage(RemoteMessage message) {
if (message.data['type'] == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
}
Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
Future<void> updateFCMToken() async {
// print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken);
});
}
}

View File

@ -0,0 +1,136 @@
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:yimaru_app/app/app.locator.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await locator<PushNotificationService>().setupFlutterNotifications();
await locator<PushNotificationService>().showNotification(message);
}
class PushNotificationService { final _messaging = FirebaseMessaging.instance;
bool _isFlutterLocalNotificationInitialized = false;
final _localNotifications = FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
// Initialize FCM token
await updateFCMToken();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Request permission
await _requestPermission();
// setup message handle
await _setupMessageHandler();
// Subscribe to all devices
subscribeToTopic('yimaru');
}
Future<void> _requestPermission() async {
await _messaging.requestPermission(
alert: true,
badge: true,
sound: true,
carPlay: false,
provisional: false,
announcement: false,
criticalAlert: false);
}
Future<void> setupFlutterNotifications() async {
if (_isFlutterLocalNotificationInitialized) {
return;
}
// Android setup
const channel = AndroidNotificationChannel(
'yimaru', // id
'Yimaru', // title
importance: Importance.high,
);
await _localNotifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
const initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// IOS setup
const initializationSettingsDarwin = DarwinInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin);
// Flutter notification setup
await _localNotifications.initialize(
settings: initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse response) {
if (response.payload == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
},
);
_isFlutterLocalNotificationInitialized = true;
}
Future<void> showNotification(RemoteMessage message) async {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
await _localNotifications.show(
id: notification.hashCode,
title: notification.title,
body: notification.body,
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails('yimaru', 'Yimaru',
enableVibration: true,
priority: Priority.high,
icon: '@mipmap/ic_launcher',
importance: Importance.high),
iOS: DarwinNotificationDetails(
presentAlert: true, presentBadge: true, presentSound: true)),
);
}
}
Future<void> _setupMessageHandler() async {
// Foreground message
FirebaseMessaging.onMessage
.listen((RemoteMessage message) => showNotification(message));
// Background message
FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage);
// Opened app
final initialMessage = await _messaging.getInitialMessage();
if (initialMessage != null) {
_handleBackgroundMessage(initialMessage);
}
}
void _handleBackgroundMessage(RemoteMessage message) {
if (message.data['type'] == 'Page') {
// navigatorKey.currentState?.pushNamed('RouteName');
}
}
Future<void> subscribeToTopic(String topic) async {
await FirebaseMessaging.instance.subscribeToTopic(topic);
}
Future<void> updateFCMToken() async {
// print('DEVICE TOKEN: ${await _messaging.getToken()}');
_messaging.onTokenRefresh.listen((newToken) {
// updateTokenOnServer(newToken);
});
}
}

View File

@ -13,12 +13,15 @@ String kCheckUrl = 'check';
String kFilesUrl = 'files';
String kUnreadUrl = 'unread';
String kApiVersionUrl = 'v1';
String kLevelsUrl = 'levels';
String kCoursesUrl = 'courses';
String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons';
@ -69,6 +72,8 @@ String kQuestionSetsUrl = 'question-sets';
String kRequestResetCode = 'sendResetCode';
String kNotificationsUrl = 'notifications';
String kSubcategoriesUrl = 'sub-categories';
String kProgressSummary = 'progress-summary';
@ -79,6 +84,8 @@ String kCoursePracticeQuestions = 'questions';
String kCatalogCoursesUrl = 'catalog-courses';
String kMarkNotificationRead = 'mark-all-read';
String kUpdateProfileImage = 'profile-picture';
String kSubscriptionsUrl = 'subscription-plans';
@ -101,8 +108,6 @@ String kLessonProgressUrl = 'api/v1/progress/videos';
String kGoogleAuthUrl = 'api/v1/auth/google/android';
String kAppleAuthUrl = 'api/v1/auth/apple';
String kCourseProgressUrl = 'api/v1/progress/courses';
String kAssessmentsUrl = 'api/v1/assessment/questions';

View File

@ -5,10 +5,10 @@ enum Voice { sample, recorded }
enum ResponseStatus { success, failure }
// Login method
enum LoginMethod { phone, email, google, apple }
enum LoginMethod { phone, email, google }
// Sign-up method
enum SignUpMethod { phone, email, google, apple }
enum SignUpMethod { phone, email, google }
// Learn practice
enum LearnPractices { course, module, lesson }
@ -50,6 +50,7 @@ enum StateObjects {
profileUpdate,
resetPassword,
learnPractice,
notifications,
courseCatalogs,
loginWithEmail,
coursePractice,
@ -61,9 +62,7 @@ enum StateObjects {
profileCompletion,
learnSubscription,
learnSubscriptions,
loginWithApple,
registerWithGoogle,
registerWithApple,
learnPracticeSample,
learnPracticeAnswer,
loginWithPhoneNumber,

View File

@ -6,7 +6,7 @@ import 'dart:ui';
import 'package:easy_localization/easy_localization.dart' show AssetLoader;
class CodegenLoader extends AssetLoader{
class CodegenLoader extends AssetLoader {
const CodegenLoader();
@override
@ -14,403 +14,431 @@ class CodegenLoader extends AssetLoader{
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 = {
"loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል",
"password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?",
"cont": "ቀጥል",
"register": "ይመዝገቡ",
"login_with_google": "በጉግል ይግቡ",
"login_with_apple": "በአፕል ይግቡ",
"or": "ወይም",
"login_with_phone": "በስልክ ቁጥር ይግቡ",
"create_account": "አዲስ መለያ ይፍጠሩ",
"already_have_account": "መለያ አለዎት?",
"login": " ይግቡ ",
"register_with_google": "በጉግል ይመዝገቡ",
"register_with_apple": "በአፕል ይመዝገቡ",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"login_with_email": "በኢሜይል ይግቡ",
"create_password": "የይለፍ ቃል ይፍጠሩ",
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
"eight_character_minimum": "ቢያንስ 8 ፊደላት",
"password_match": "የይለፍ ቃሉ ተመሳስሏል",
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
"terms_of_services": "የአገልግሎት ውሎች",
"and": "እና",
"privacy_policy": "የግላዊነት ፖሊሲ",
"register_with_email": "በኢሜል ይመዝገቡ",
"verification_code": "የማረጋገጫ ኮድ",
"resend_code": "ኮዱን እንደገና ላክ",
"code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል",
"code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል",
"resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ",
"reset_password": " የይለፍ ቃልን ይቀይሩ",
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።",
"please_wait": "እባክዎ ይጠብቁ",
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል",
"reset_code": " የመቀየሪያ ኮድ ",
"new_password": "አዲስ የይለፍ ቃል",
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
"view_course": " ኮርሱን ይመልከቱ",
"continue_learning": "መማርን ይቀጥሉ",
"start_learning": "ትምህርትን ይጀምሩ",
"completed": "ተጠናቋል",
"take_practice": "ልምምድ ያድርጉ",
"your_current_level": "የአሁኑ ደረጃዎ",
"overall_progress": "አጠቃላይ እድገት",
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው",
"view_module": "ሞጁሉን ይመልከቱ",
"progress": "እድገት",
"keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
"lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
"practice": "ልምምድ",
"start": "ጀምር",
"in_progress": "በሂደት ላይ",
"hello": "ሰላም",
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
"learn": "ይማሩ ",
"course": "ኮርስ",
"profile": " ፕሮፋይል ",
"speaking_partner": "የንግግር ጓደኛ",
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
"start_practice": "ልምምድ ጀምር",
"almost_there": "ሊጨርሱ ተቃርበዋል",
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
"continue_practice": "ልምምዱን ይቀጥሉ",
"end_session": "ክፍለ ጊዜውን ያብቁ ",
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
"practice_speaking": "ንግግርን ይለማመዱ",
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
"reply": "እንደገና አዳምጥ",
"cancel": "ይቅር",
"you_are_speaking": "እየተናገሩ ነው",
"practice_completed": "ልምምዱ ተጠናቅቋል",
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
"practice_again": "እንደገና ይለማመዱ",
"conversation_review": "የንግግር ግምገማ",
"result": "ውጤት",
"quick_tip": "ጠቃሚ ምክር",
"retry": "እንደገና ይሞክሩ",
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
"hi": "ሰላም",
"edit_profile": "መገለጫ ያስተካክሉ",
"first_name": "የመጀመሪያ ስም",
"last_name": "የአባት ስም",
"gender": "ፆታ",
"male": "ወንድ",
"female": "ሴት",
"phone_number": "የስልክ ቁጥር",
"country": "ሀገር",
"region": "ክልል",
"select_region": "ክልል ይምረጡ",
"enter_your_city": "ከተማዎን ያስገቡ",
"occupation": "የስራ መስክ",
"select_occupation": "ሙያዎን ይምረጡ",
"save_changes": "ለውጦችን ያስቀምጡ",
"my_progress": "የእኔ እድገት",
"track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ",
"account_and_privacy": "መለያ እና ግላዊነት",
"manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ",
"support": "ድጋፍ",
"get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ",
"logout": "ውጣ",
"app_settings": "የመተግበሪያ ቅንብሮች",
"legal_and_information": "ሕጋዊ እና መረጃ",
"change_language": "ቋንቋ ቀይር",
"terms_and_conditions": "ውሎች እና ሁኔታዎች",
"delete_account": "መለያ ሰርዝ",
"language_preference": "የቋንቋ ምርጫ",
"choose_your_language": "ለውጦችን አስቀምጥ",
"switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ",
"need_help": "እገዛ ይፈልጋሉ?",
"call_support": "የስልክ ድጋፍ",
"talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ",
"telegram_support": "የቴሌግራም ድጋፍ",
"chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ",
"call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ",
"tap_to_call": "ለመደወል ይንኩ",
"join_telegram": "በቴሌግራም የይማሩ አካዳሚን ይቀላቀሉ",
"connect_with_support_team": "ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።",
"open_in_telegram": "በቴሌግራም ይክፈቱ",
"search_for": "ፈልጉት",
"current_level": "የአሁኑ ደረጃ",
"keep_up_the_great_work": "በጣም ጥሩ እየሰራህ ነው! ቀጥልበት፣ አስደናቂ ነህ።",
"no_practice_available": "ምንም ልምምድ አልተገኘም!",
"begin_module_practice": "የሞጁሉን ልምምድ ጀምር",
"lets_practice_lesson": "እንለማመድ",
"lets_quickly_review": "በዚህ ሞጁል ውስጥ የተማርከውን በፍጥነት እንከልስ!",
"lets_practice_module": "አሁን የተማርከውን እንለማመድ!",
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ",
"sample_answer": "ናሙና መልስ",
"your_answer": "መልስህ",
"sound_confident": "በዚህ ጊዜ የበለጠ እምነት ያለህ ይመስላል — በጣም ጥሩ መሻሻል ነው!",
"you_have_completed": "አያይ! አጠናቀቅህ",
"yes": "አዎ",
"no": "አይ",
"want_to_quit": "ለመውጣት እርግጠኛ ነህ?",
"required_field": "ይህ መስክ ያስፈልጋል",
"enter_full_name": "ሙሉ ስምህን አስገባ",
"invalid_email": "የማይሰራ የኢሜይል ቅርጸት",
"phone_must_start_with": "የስልክ ቁጥር በ251 መጀመር አለበት",
"phone_must_be": "የስልክ ቁጥር 12 አሃዞች መሆን አለበት",
"what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ",
"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, Map<String,dynamic>> mapLocales = {"en": _en, "am": _am};
static const Map<String, dynamic> _am = {
"loading": "በመጫን ላይ",
"welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "መለያ የለዎትም?",
"email": "ኢሜይል",
"password": "የይለፍ ቃል",
"forgot_password": "የይለፍ ቃል ረሱ?",
"cont": "ቀጥል",
"register": "ይመዝገቡ",
"login_with_google": "በጉግል ይግቡ",
"or": "ወይም",
"login_with_phone": "በስልክ ቁጥር ይግቡ",
"create_account": "አዲስ መለያ ይፍጠሩ",
"already_have_account": "መለያ አለዎት?",
"login": " ይግቡ ",
"register_with_google": "በጉግል ይመዝገቡ",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"login_with_email": "በኢሜይል ይግቡ",
"create_password": "የይለፍ ቃል ይፍጠሩ",
"confirm_password": "የይለፍ ቃል ያረጋግጡ",
"eight_character_minimum": "ቢያንስ 8 ፊደላት",
"password_match": "የይለፍ ቃሉ ተመሳስሏል",
"sign_up_agreement":
"‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።",
"terms_of_services": "የአገልግሎት ውሎች",
"and": "እና",
"privacy_policy": "የግላዊነት ፖሊሲ",
"register_with_email": "በኢሜል ይመዝገቡ",
"verification_code": "የማረጋገጫ ኮድ",
"resend_code": "ኮዱን እንደገና ላክ",
"code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል",
"code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል",
"resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ",
"reset_password": " የይለፍ ቃልን ይቀይሩ",
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።",
"please_wait": "እባክዎ ይጠብቁ",
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል",
"reset_code": " የመቀየሪያ ኮድ ",
"new_password": "አዲስ የይለፍ ቃል",
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
"view_course": " ኮርሱን ይመልከቱ",
"continue_learning": "መማርን ይቀጥሉ",
"start_learning": "ትምህርትን ይጀምሩ",
"completed": "ተጠናቋል",
"take_practice": "ልምምድ ያድርጉ",
"your_current_level": "የአሁኑ ደረጃዎ",
"overall_progress": "አጠቃላይ እድገት",
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው",
"view_module": "ሞጁሉን ይመልከቱ",
"progress": "እድገት",
"keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
"lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
"practice": "ልምምድ",
"start": "ጀምር",
"in_progress": "በሂደት ላይ",
"hello": "ሰላም",
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
"learn": "ይማሩ ",
"course": "ኮርስ",
"profile": " ፕሮፋይል ",
"speaking_partner": "የንግግር ጓደኛ",
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
"start_practice": "ልምምድ ጀምር",
"almost_there": "ሊጨርሱ ተቃርበዋል",
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
"continue_practice": "ልምምዱን ይቀጥሉ",
"end_session": "ክፍለ ጊዜውን ያብቁ ",
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
"practice_speaking": "ንግግርን ይለማመዱ",
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
"reply": "እንደገና አዳምጥ",
"cancel": "ይቅር",
"you_are_speaking": "እየተናገሩ ነው",
"practice_completed": "ልምምዱ ተጠናቅቋል",
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
"practice_again": "እንደገና ይለማመዱ",
"conversation_review": "የንግግር ግምገማ",
"result": "ውጤት",
"quick_tip": "ጠቃሚ ምክር",
"retry": "እንደገና ይሞክሩ",
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
"view_profile": "ፕሮፋይሎን ይመልከቱ ",
"hi": "ሰላም",
"edit_profile": "መገለጫ ያስተካክሉ",
"first_name": "የመጀመሪያ ስም",
"last_name": "የአባት ስም",
"gender": "ፆታ",
"male": "ወንድ",
"female": "ሴት",
"phone_number": "የስልክ ቁጥር",
"country": "ሀገር",
"region": "ክልል",
"select_region": "ክልል ይምረጡ",
"enter_your_city": "ከተማዎን ያስገቡ",
"occupation": "የስራ መስክ",
"select_occupation": "ሙያዎን ይምረጡ",
"save_changes": "ለውጦችን ያስቀምጡ",
"my_progress": "የእኔ እድገት",
"track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ",
"account_and_privacy": "መለያ እና ግላዊነት",
"manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ",
"support": "ድጋፍ",
"get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ",
"logout": "ውጣ",
"app_settings": "የመተግበሪያ ቅንብሮች",
"legal_and_information": "ሕጋዊ እና መረጃ",
"change_language": "ቋንቋ ቀይር",
"terms_and_conditions": "ውሎች እና ሁኔታዎች",
"delete_account": "መለያ ሰርዝ",
"language_preference": "የቋንቋ ምርጫ",
"choose_your_language": "ለውጦችን አስቀምጥ",
"switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ",
"need_help": "እገዛ ይፈልጋሉ?",
"call_support": "የስልክ ድጋፍ",
"talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ",
"telegram_support": "የቴሌግራም ድጋፍ",
"chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ",
"call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ",
"tap_to_call": "ለመደወል ይንኩ",
"join_telegram": "በቴሌግራም የይማሩ አካዳሚን ይቀላቀሉ",
"connect_with_support_team":
"ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።",
"open_in_telegram": "በቴሌግራም ይክፈቱ",
"search_for": "ፈልጉት",
"current_level": "የአሁኑ ደረጃ",
"keep_up_the_great_work": "በጣም ጥሩ እየሰራህ ነው! ቀጥልበት፣ አስደናቂ ነህ።",
"no_practice_available": "ምንም ልምምድ አልተገኘም!",
"begin_module_practice": "የሞጁሉን ልምምድ ጀምር",
"lets_practice_lesson": "እንለማመድ",
"lets_quickly_review": "በዚህ ሞጁል ውስጥ የተማርከውን በፍጥነት እንከልስ!",
"lets_practice_module": "አሁን የተማርከውን እንለማመድ!",
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "እየተናገረ ነው",
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "ውጤቶቼን እይ",
"sample_answer": "ናሙና መልስ",
"your_answer": "መልስህ",
"sound_confident": "በዚህ ጊዜ የበለጠ እምነት ያለህ ይመስላል — በጣም ጥሩ መሻሻል ነው!",
"you_have_completed": "አያይ! አጠናቀቅህ",
"yes": "አዎ",
"no": "አይ",
"want_to_quit": "ለመውጣት እርግጠኛ ነህ?",
"required_field": "ይህ መስክ ያስፈልጋል",
"enter_full_name": "ሙሉ ስምህን አስገባ",
"invalid_email": "የማይሰራ የኢሜይል ቅርጸት",
"phone_must_start_with": "የስልክ ቁጥር በ251 መጀመር አለበት",
"phone_must_be": "የስልክ ቁጥር 12 አሃዞች መሆን አለበት",
"what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "ጾታህን ምረጥ",
"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": "የእድገት መቶኛ",
"notifications": "ማሳወቂያዎች"
};
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",
"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_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",
"notifications": "Notifications"
};
static const Map<String, Map<String, dynamic>> mapLocales = {
"am": _am,
"en": _en
};
}

View File

@ -2,7 +2,7 @@
// ignore_for_file: constant_identifier_names
abstract class LocaleKeys {
abstract class LocaleKeys {
static const loading = 'loading';
static const welcome_back = 'welcome_back';
static const checking_user_info = 'checking_user_info';
@ -13,14 +13,12 @@ abstract class LocaleKeys {
static const cont = 'cont';
static const register = 'register';
static const login_with_google = 'login_with_google';
static const login_with_apple = 'login_with_apple';
static const or = 'or';
static const login_with_phone = 'login_with_phone';
static const create_account = 'create_account';
static const already_have_account = 'already_have_account';
static const login = 'login';
static const register_with_google = 'register_with_google';
static const register_with_apple = 'register_with_apple';
static const register_with_phone = 'register_with_phone';
static const enter_phone_number = 'enter_phone_number';
static const login_with_email = 'login_with_email';
@ -45,10 +43,10 @@ abstract class LocaleKeys {
static const reset_code = 'reset_code';
static const new_password = 'new_password';
static const logged_in_successfully = 'logged_in_successfully';
static const view_course = 'view_course';
static const continue_learning = 'continue_learning';
static const start_learning = 'start_learning';
static const completed = 'completed';
static const view_course = 'view_course';
static const take_practice = 'take_practice';
static const your_current_level = 'your_current_level';
static const overall_progress = 'overall_progress';
@ -163,7 +161,8 @@ abstract class LocaleKeys {
static const educational_background = 'educational_background';
static const education_for_personalization = 'education_for_personalization';
static const your_occupation = 'your_occupation';
static const occupation_for_personalization = 'occupation_for_personalization';
static const occupation_for_personalization =
'occupation_for_personalization';
static const location = 'location';
static const select_country_region = 'select_country_region';
static const select_country = 'select_country';
@ -190,13 +189,15 @@ abstract class LocaleKeys {
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 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';
static const notifications = 'notifications';
}

View File

@ -333,6 +333,12 @@ TextStyle style14LG400 = const TextStyle(
color: kcLightGrey,
);
TextStyle style12W600 = const TextStyle(
fontSize: 12,
color: kcWhite,
fontWeight: FontWeight.w600
);
TextStyle style14MG400 = const TextStyle(
color: kcMediumGrey,
);

View File

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

View File

@ -5,6 +5,7 @@ import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/user.dart';
import '../../../services/authentication_service.dart';
import '../../../services/in_app_notification_service.dart';
class CourseViewModel extends ReactiveViewModel {
// Dependency injection
@ -13,15 +14,23 @@ class CourseViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_authenticationService];
[_authenticationService,_inAppNotificationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Notification count
int get _unreadCount => _inAppNotificationService.unreadCount;
int get unreadCount => _unreadCount;
// Course
final List<Map<String, dynamic>> _courses = [
{
@ -41,6 +50,11 @@ class CourseViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToNotification() async =>
await _navigationService.navigateToNotificationView();
Future<void> navigateToCourseCatalog() async =>
await _navigationService.navigateToCourseCatalogView();
}

View File

@ -1,6 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/failure/screens/first_failure_screen.dart';
import 'package:yimaru_app/ui/views/failure/screens/second_failure_screen.dart';
import 'package:yimaru_app/ui/views/failure/screens/third_failure_screen.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
@ -26,94 +30,38 @@ class FailureView extends StackedView<FailureViewModel> {
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(),
backgroundColor: kcPrimaryColor,
body: _buildStartupScreens(),
);
Widget _buildScaffold() => Stack(
children: _buildScaffoldChildren(),
Widget _buildStartupScreens() => FlutterCarousel(
options: FlutterCarouselOptions(
autoPlay: true,
viewportFraction: 1,
showIndicator: false,
height: double.maxFinite,
),
items: _buildScreens(),
);
List<Widget> _buildScaffoldChildren() => [
_buildBackground(),
_buildColumn(),
List<Widget> _buildScreens() => [
_buildFirstFailure(),
_buildSecondFailure(),
_buildThirdFailure(),
];
Widget _buildBackground() => Image.asset(
'assets/images/loading.png',
fit: BoxFit.fill,
width: double.maxFinite,
height: double.maxFinite,
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[_buildIconWrapper(), _buildSafeWrapper()];
Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 100),
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset('assets/icons/logo.svg', height: 50);
Widget _buildSafeWrapper() => SafeArea(child: _buildBottomSectionWrapper());
Widget _buildBottomSectionWrapper() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildBottomSectionColumn(),
);
Widget _buildBottomSectionColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildBottomSectionChildren(),
);
List<Widget> _buildBottomSectionChildren() => [
_buildLoadingTextWrapper(),
verticalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16));
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildRetryButtonWrapper() => GestureDetector(
Widget _buildFirstFailure() => FirstFailureScreen(
label: label,
onTap: onTap,
child: _buildRetryButton(),
);
Widget _buildRetryButton() => Text(
'Retry',
style: style16W600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
Widget _buildSecondFailure() => SecondFailureScreen(
label: label,
onTap: onTap,
);
Widget _buildThirdFailure() => ThirdFailureScreen(
label: label,
onTap: onTap,
);
}

View File

@ -0,0 +1,165 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart';
import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
class FirstFailureScreen extends ViewModelWidget<FailureViewModel> {
final String label;
final GestureTapCallback onTap;
const FirstFailureScreen({super.key,required this.onTap,required this.label});
@override
Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_white.svg',
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25W600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25W400,
),
TextSpan(
text: ' ሰዓት ',
style: style25W600,
),
TextSpan(
text: 'ይማሩ!',
style: style25W400,
),
],
),
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_1.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
horizontalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingText() =>
Text('$label...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap,
child: _buildRetryButton(),
);
Widget _buildRetryButton() => Text(
'Retry',
style: style16W600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
}

View File

@ -0,0 +1,161 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/failure/failure_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
class SecondFailureScreen extends ViewModelWidget<FailureViewModel> {
final String label;
final GestureTapCallback onTap;
const SecondFailureScreen(
{super.key, required this.onTap, required this.label});
@override
Widget build(BuildContext context, FailureViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: Colors.amber,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren() =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_purple.svg',
height: 25,
);
Widget _buildLowerColumnWrapper() => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren() => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' እድሜ ',
style: style25P600,
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_2.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper() => SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper() => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
verticalSpaceSmall,
_buildRetryButtonWrapper()
];
Widget _buildLoadingText() => Text('$label...', style: style16P600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcPrimaryColor);
Widget _buildRetryButtonWrapper() => GestureDetector(
onTap: onTap,
child: _buildRetryButton(),
);
Widget _buildRetryButton() => Text(
'Retry',
style: style16P600.copyWith(
fontStyle: FontStyle.italic, decoration: TextDecoration.underline),
);
}

View File

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

View File

@ -4,6 +4,8 @@ import 'package:stacked/stacked.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/views/course/course_view.dart';
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
@ -17,6 +19,8 @@ class HomeView extends StackedView<HomeViewModel> {
@override
void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.inAppUpdate();
await viewModel.getUnreadNotifications();
super.onViewModelReady(viewModel);
}
@ -80,7 +84,7 @@ class HomeView extends StackedView<HomeViewModel> {
case 0:
return const LearnProgramView();
case 1:
return const ComingSoon();
return const CourseView();
default:
return const ProfileView();

View File

@ -8,13 +8,13 @@ import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../services/authentication_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/in_app_update_service.dart';
class HomeViewModel extends ReactiveViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>();
@ -22,9 +22,11 @@ class HomeViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_authenticationService];
[_authenticationService,_inAppNotificationService];
// Current user
User? get _user => _authenticationService.user;
@ -55,8 +57,6 @@ class HomeViewModel extends ReactiveViewModel {
rebuildUi();
}
// Remote api calls
// In-app update
@ -65,4 +65,12 @@ class HomeViewModel extends ReactiveViewModel {
await _inAppUpdateService.checkForUpdate();
}
}
// Unread notifications
Future<void> getUnreadNotifications() async {
if (await _statusChecker.checkConnection()) {
await _inAppNotificationService.getUnreadNotifications();
}
}
}

View File

@ -3,7 +3,6 @@ import 'package:flutter_carousel_widget/flutter_carousel_widget.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/fourth_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/second_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/third_landing_screen.dart';
@ -44,17 +43,15 @@ class LandingView extends StackedView<LandingViewModel> {
);
List<Widget> _buildScreens() => [
_buildFirstWelcome(),
_buildSecondWelcome(),
_buildThirdWelcome(),
_buildFourthWelcome()
_buildFirstLanding(),
_buildSecondLanding(),
_buildThirdLanding(),
];
Widget _buildFirstWelcome() => const FirstLandingScreen();
Widget _buildFirstLanding() => const FirstLandingScreen();
Widget _buildSecondWelcome() => const SecondLandingScreen();
Widget _buildSecondLanding() => const SecondLandingScreen();
Widget _buildThirdWelcome() => const ThirdLandingScreen();
Widget _buildThirdLanding() => const ThirdLandingScreen();
Widget _buildFourthWelcome() => const FourthLandingScreen();
}

View File

@ -49,7 +49,7 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
'assets/icons/logo_white.svg',
height: 25,
);
@ -100,7 +100,7 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildImage() => Image.asset(
'assets/images/landing_1.jpg',
'assets/images/landing_1.png',
fit: BoxFit.cover,
);
@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Next',
borderRadius: 25,
onTap: viewModel.next,
text: 'Get Started',
backgroundColor: kcWhite,
foregroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
);
}

View File

@ -1,136 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
import '../landing_viewmodel.dart';
class FourthLandingScreen extends ViewModelWidget<LandingViewModel> {
const FourthLandingScreen({super.key});
@override
Widget build(BuildContext context, LandingViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LandingViewModel viewModel) => Scaffold(
backgroundColor: Colors.amber,
body: _buildScaffoldPadding(viewModel),
);
Widget _buildScaffoldPadding(LandingViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(viewModel),
);
Widget _buildScaffold(LandingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(viewModel),
);
List<Widget> _buildScaffoldChildren(LandingViewModel viewModel) =>
[_buildUpperColumn(), _buildLowerColumnWrapper(viewModel)];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
color: kcPrimaryColor,
height: 25,
);
Widget _buildLowerColumnWrapper(LandingViewModel viewModel) => Expanded(
child: _buildLowerColumn(viewModel),
);
Widget _buildLowerColumn(LandingViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(viewModel),
);
List<Widget> _buildLowerColumnChildren(LandingViewModel viewModel) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper(viewModel)
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25P600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25P400,
),
TextSpan(
text: ' እድሜ ',
style: style25P600,
),
TextSpan(
text: 'ይማሩ!',
style: style25P400,
),
],
),
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_2.jpg',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper(LandingViewModel viewModel) =>
SafeArea(child: _buildContinueButtonWrapper(viewModel));
Widget _buildContinueButtonWrapper(LandingViewModel viewModel) => Align(
alignment: Alignment.bottomCenter,
child: _buildButtonContainer(viewModel),
);
Widget _buildButtonContainer(LandingViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButtonState(viewModel),
);
Widget _buildContinueButtonState(LandingViewModel viewModel) =>
viewModel.isBusy ? _buildIndicator() : _buildContinueButton(viewModel);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 25,
text: 'Get Started',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
);
}

View File

@ -49,7 +49,7 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
'assets/icons/logo_purple.svg',
color: kcPrimaryColor,
height: 25,
);
@ -101,7 +101,7 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildImage() => Image.asset(
'assets/images/landing_2.jpg',
'assets/images/landing_2.png',
fit: BoxFit.cover,
);
@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Next',
borderRadius: 25,
onTap: viewModel.next,
text: 'Get Started',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
);
}

View File

@ -49,7 +49,7 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
'assets/icons/logo_purple.svg',
color: kcPrimaryColor,
height: 25,
);
@ -101,7 +101,7 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
);
Widget _buildImage() => Image.asset(
'assets/images/landing_3.jpg',
'assets/images/landing_3.png',
fit: BoxFit.cover,
);
@ -127,10 +127,10 @@ class ThirdLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Next',
borderRadius: 25,
onTap: viewModel.next,
text: 'Get Started',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
);
}

View File

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

View File

@ -13,14 +13,16 @@ import 'learn_course_viewmodel.dart';
class LearnCourseView extends StackedView<LearnCourseViewModel> {
final int id;
final bool first;
const LearnCourseView({Key? key, required this.id}) : super(key: key);
const LearnCourseView({Key? key, required this.id, required this.first})
: super(key: key);
Future<void> _onPractice(
{required BuildContext context,
required LearnCourse course,
required LearnCourseViewModel viewModel}) async {
if (course.access?.completedCount == course.access?.totalCount) {
if (course.access?.isCompleted ?? false) {
await viewModel.navigateToLearnPractice(
id: course.id ?? 0, level: course.name ?? '');
} else {
@ -150,8 +152,8 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
context: context,
viewModel: viewModel,
course: viewModel.courses[index]),
onViewTap: () async =>
await viewModel.navigateToLearnModule(viewModel.courses[index]),
onViewTap: () async => await viewModel.navigateToLearnModule(
first: first && index == 0, course: viewModel.courses[index]),
),
separatorBuilder: (context, index) => verticalSpaceSmall,
);

View File

@ -32,8 +32,10 @@ class LearnCourseViewModel extends ReactiveViewModel {
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course);
Future<void> navigateToLearnModule(
{required bool first, required LearnCourse course}) async =>
_navigationService.navigateToLearnModuleView(
first: first, course: course);
Future<void> navigateToLearnPractice(
{required int id, required String level}) async =>

View File

@ -17,32 +17,25 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_viewmodel.dart';
class LearnLessonView extends StackedView<LearnLessonViewModel> {
final bool first;
final LearnModule module;
const LearnLessonView({Key? key, required this.module}) : super(key: key);
const LearnLessonView({Key? key, required this.first, 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() == 'active') {
if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
}
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'active') {
if (lesson.access?.isAccessible ?? false) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await viewModel.navigateToLearnSubscription();
await _showSheet(context: context, viewModel: viewModel);
}
} else {
if (lesson.access?.isAccessible ?? false) {
if (first && index < 3) {
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await _showSheet(context: context, viewModel: viewModel);
@ -224,6 +217,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
index: index,
first: first && index < 3,
lesson: viewModel.lessons[index],
last: index == viewModel.lessons.length - 1,
onPracticeTap: () async => await _onPractice(
@ -244,12 +238,14 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
Widget _buildTile({
required bool last,
required int index,
required bool first,
required LearnLesson lesson,
required GestureTapCallback? onLessonTap,
required GestureTapCallback? onPracticeTap,
}) =>
LearnLessonTile(
last: last,
first: first,
index: index,
lesson: lesson,
onLessonTap: onLessonTap,

View File

@ -31,21 +31,8 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Future<void> _onPractice(
{required LearnLesson lesson,
required LearnLessonDetailViewModel viewModel}) async {
/* await viewModel.pause();
await viewModel.pause();
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
*/
if (index > 1) {
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'active') {
await viewModel.pause();
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
} else {
await viewModel.pause();
await viewModel.navigateToLearnSubscription();
}
} else {
await viewModel.pause();
await viewModel.navigateToLearnPractice(lesson.id ?? 0);
}
}
@override

View File

@ -16,27 +16,26 @@ import '../../widgets/small_app_bar.dart';
import 'learn_module_viewmodel.dart';
class LearnModuleView extends StackedView<LearnModuleViewModel> {
final bool first;
final LearnCourse course;
const LearnModuleView({Key? key, required this.course}) : super(key: key);
const LearnModuleView({Key? key, required this.first, 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) {
if (module.access?.isCompleted ?? false ) {
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,
@ -201,8 +200,8 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
viewModel: viewModel,
module: viewModel.modules[index],
),
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
onModuleTap: () async => await viewModel.navigateToLearnLesson(
first: first && index == 0, module: viewModel.modules[index]),
),
);

View File

@ -35,8 +35,10 @@ class LearnModuleViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module);
Future<void> navigateToLearnLesson(
{required bool first, required LearnModule module}) async =>
await _navigationService.navigateToLearnLessonView(
first: first, module: module);
Future<void> navigateToLearnPractice(
{required int id, required String module}) async =>

View File

@ -10,6 +10,7 @@ import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/learn_question.dart';
import '../../../services/api_service.dart';
import '../../../services/audio_player_service.dart';
@ -264,6 +265,9 @@ class LearnPracticeViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
// Remote api call
// Refresh url

View File

@ -27,6 +27,14 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
required this.subtitle,
required this.practice});
Future<void> _practice(LearnPracticeViewModel viewModel) async {
if (viewModel.user?.subscriptionStatus?.toLowerCase() == 'active') {
viewModel.goTo(1);
} else {
await viewModel.navigateToLearnSubscription();
}
}
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.pop();
@ -207,7 +215,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
text: label,
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor,
onTap: () async => await _practice(viewModel),
);
}

View File

@ -61,6 +61,8 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
Widget _buildAppBar(LearnProgramViewModel viewModel) => ProfileAppBar(
name: viewModel.user?.firstName,
profileImage: viewModel.user?.profilePicture,
unreadCount: viewModel.unreadCount.toString(),
onTap: () async => await viewModel.navigateToNotification(),
);
Widget _buildProgramsColumnWrapper(LearnProgramViewModel viewModel) =>
@ -87,8 +89,8 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildTile(
program: viewModel.learnPrograms[index],
onTap: () async => await viewModel
.navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0),
onTap: () async => await viewModel.navigateToLearnCourse(
first: index == 0, id: viewModel.learnPrograms[index].id ?? 0),
),
);

View File

@ -6,6 +6,7 @@ import 'package:yimaru_app/models/learn_program.dart';
import '../../../app/app.locator.dart';
import '../../../models/user.dart';
import '../../../services/authentication_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
@ -21,23 +22,34 @@ class LearnProgramViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService];
[_learnService, _authenticationService,_inAppNotificationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Notification count
int get _unreadCount => _inAppNotificationService.unreadCount;
int get unreadCount => _unreadCount;
// Learn programs
List<LearnProgram> get _learnPrograms => _learnService.programs;
List<LearnProgram> get learnPrograms => _learnPrograms;
// Navigation
Future<void> navigateToLearnCourse(int id) async =>
_navigationService.navigateToLearnCourseView(id: id);
Future<void> navigateToNotification() async =>
await _navigationService.navigateToNotificationView();
Future<void> navigateToLearnCourse(
{required int id, required bool first}) async =>
_navigationService.navigateToLearnCourseView(id: id, first: first);
// Remote api call

View File

@ -7,7 +7,6 @@ import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/apple_auth_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart';
@ -26,23 +25,19 @@ class LoginViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>();
final _appleAuthService = locator<AppleAuthService>();
final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_googleAuthService, _appleAuthService, _localizationService];
[_googleAuthService, _localizationService];
// Google user
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
GoogleSignInAccount? get googleUser => _googleUser;
bool get isAppleSignInAvailable => _appleAuthService.isSupported;
// Languages
Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage;
@ -240,50 +235,6 @@ class LoginViewModel extends ReactiveViewModel
Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(),
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
Future<void> loginWithPhoneNumber() async =>
await runBusyFuture(_loginWithPhoneNumber(),

View File

@ -66,8 +66,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
Stack(children: [
_buildScaffold(context: context, viewModel: viewModel),
_buildLoginWithEmailState(viewModel),
_buildLoginWithGoogleState(viewModel),
_buildLoginWithAppleState(viewModel)
_buildLoginWithGoogleState(viewModel)
]);
Widget _buildScaffold(
@ -233,8 +232,6 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
List<Widget> _buildLowerColumnChildren(LoginViewModel viewModel) => [
_buildContinueButton(viewModel),
_buildLoginWithGoogleButton(viewModel),
if (viewModel.isAppleSignInAvailable)
_buildLoginWithAppleButton(viewModel),
_buildOptionTextDivider(),
_buildLoginWithPhoneButton(viewModel),
verticalSpaceMedium
@ -268,18 +265,6 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
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 _buildLoginWithPhoneButton(LoginViewModel viewModel) =>
@ -302,9 +287,4 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
viewModel.busy(StateObjects.loginWithGoogle)
? const PageLoadingIndicator()
: Container();
Widget _buildLoginWithAppleState(LoginViewModel viewModel) =>
viewModel.busy(StateObjects.loginWithApple)
? const PageLoadingIndicator()
: Container();
}

View File

@ -0,0 +1,112 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/notification_card.dart';
import '../../../models/in_app_notification.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/translations/locale_keys.g.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/small_app_bar.dart';
import 'notification_viewmodel.dart';
class NotificationView extends StackedView<NotificationViewModel> {
const NotificationView({Key? key}) : super(key: key);
@override
void onViewModelReady(NotificationViewModel viewModel) async {
await viewModel.getAllNotifications();
await viewModel.markNotificationsRead();
super.onViewModelReady(viewModel);
}
@override
NotificationViewModel viewModelBuilder(BuildContext context) =>
NotificationViewModel();
@override
Widget builder(
BuildContext context,
NotificationViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(NotificationViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffoldContainer(viewModel),
);
Widget _buildScaffoldContainer(NotificationViewModel viewModel) => Container(
decoration: bgDecoration,
child: _buildScaffold(viewModel),
);
Widget _buildScaffold(NotificationViewModel viewModel) =>
SafeArea(child: _buildBodyWrapper(viewModel));
Widget _buildBodyWrapper(NotificationViewModel viewModel) =>
_buildBody(viewModel);
Widget _buildBody(NotificationViewModel viewModel) => _buildColumn(viewModel);
Widget _buildColumn(NotificationViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(viewModel),
);
List<Widget> _buildColumnChildren(NotificationViewModel viewModel) => [
verticalSpaceMedium,
_buildAppBarWrapper(viewModel),
verticalSpaceMedium,
_buildNotificationsColumnWrapper(viewModel)
];
Widget _buildAppBarWrapper(NotificationViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildAppbar(viewModel),
);
Widget _buildAppbar(NotificationViewModel viewModel) => SmallAppBar(
showBackButton: true,
onPop: viewModel.pop,
title: LocaleKeys.notifications.tr(),
);
Widget _buildNotificationsColumnWrapper(NotificationViewModel viewModel) =>
Expanded(child: _buildNotificationsColumnScrollView(viewModel));
Widget _buildNotificationsColumnScrollView(NotificationViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(NotificationViewModel viewModel) =>
viewModel.busy(StateObjects.notifications)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(NotificationViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.notifications.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildCard(
notification: viewModel.notifications[index],
),
);
Widget _buildCard({
required InAppNotification notification,
}) =>
NotificationCard(notification: notification);
}

View File

@ -0,0 +1,72 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../models/in_app_notification.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/localization_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class NotificationViewModel extends ReactiveViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _localizationService = locator<LocalizationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_localizationService, _inAppNotificationService];
// Languages
Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage;
Map<String, dynamic> get selectedLanguage => _selectedLanguage;
// Notifications
List<InAppNotification> get _notifications =>
_inAppNotificationService.notifications;
List<InAppNotification> get notifications => _notifications;
// Notification count
int get _unreadCount => _inAppNotificationService.unreadCount;
int get unreadCount => _unreadCount;
// Navigation
void pop() => _navigationService.back();
// Remote api call
// Notifications
Future<void> getAllNotifications() async =>
await runBusyFuture(_getAllNotifications(),
busyObject: StateObjects.notifications);
Future<void> _getAllNotifications() async {
if (await _statusChecker.checkConnection()) {
await _inAppNotificationService.getAllNotifications();
}
}
Future<void> getUnreadNotifications() async =>
await runBusyFuture(_getUnreadNotifications());
Future<void> _getUnreadNotifications() async {
if (await _statusChecker.checkConnection()) {
await _inAppNotificationService.getUnreadNotifications();
}
}
Future<void> markNotificationsRead() async {
if (await _statusChecker.checkConnection()) {
await _inAppNotificationService.markNotificationRead();
}
}
}

View File

@ -205,8 +205,8 @@ class OnboardingViewModel extends ReactiveViewModel
_selectedCountry = value;
if (value?.code?.toLowerCase().trim() == 'et') {
_dropdownRegion = true;
_selectedRegion = _regions
.firstWhere((e) => e.code?.toLowerCase().trim().contains('addis_ababa') ?? false);
_selectedRegion = _regions.firstWhere(
(e) => e.code?.toLowerCase().trim().contains('addis_ababa') ?? false);
} else {
_dropdownRegion = false;
}

View File

@ -26,7 +26,8 @@ class PaymentView extends StackedView<PaymentViewModel> {
@override
void onViewModelReady(PaymentViewModel viewModel) async {
await viewModel.createLearnSubscriptionRequest(phone: phone,subscription: subscription);
await viewModel.createLearnSubscriptionRequest(
phone: phone, subscription: subscription);
super.onViewModelReady(viewModel);
}

View File

@ -45,18 +45,22 @@ class PaymentViewModel extends ReactiveViewModel {
// Remote api call
// Learn subscription
Future<void> createLearnSubscriptionRequest({required String phone,required LearnSubscription subscription}) async =>
await runBusyFuture(_createLearnSubscriptionRequest(phone: phone,subscription: subscription),
Future<void> createLearnSubscriptionRequest(
{required String phone,
required LearnSubscription subscription}) async =>
await runBusyFuture(
_createLearnSubscriptionRequest(
phone: phone, subscription: subscription),
busyObject: StateObjects.learnSubscription);
Future<void> _createLearnSubscriptionRequest({required String phone,required LearnSubscription subscription}) async {
Future<void> _createLearnSubscriptionRequest(
{required String phone, required LearnSubscription subscription}) async {
if (await _statusChecker.checkConnection()) {
Map<String, dynamic> data = {
'provider': 'CHAPA',
'phone': '251$phone',
'email': 'test@gmail.com',
'plan_id': subscription.id,
};
Map<String, dynamic> response =

View File

@ -5,6 +5,7 @@ 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/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/notification_icon.dart';
import 'package:yimaru_app/ui/widgets/profile_card.dart';
import 'package:yimaru_app/ui/widgets/profile_image.dart';
import 'package:yimaru_app/ui/widgets/view_profile_button.dart';
@ -89,8 +90,9 @@ class ProfileView extends StackedView<ProfileViewModel> {
required ProfileViewModel viewModel}) =>
Column(
children: [
verticalSpaceSmall,
verticalSpaceMedium,
_buildNotificationIconWrapper(),
_buildNotificationIconWrapper(viewModel),
_buildProfileSection(context: context, viewModel: viewModel),
verticalSpaceSmall,
_buildViewProfileButton(viewModel),
@ -102,12 +104,10 @@ class ProfileView extends StackedView<ProfileViewModel> {
],
);
Widget _buildNotificationIconWrapper() =>
Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon());
Widget _buildNotificationIcon() => const Icon(
Icons.notifications_none,
color: kcDarkGrey,
Widget _buildNotificationIconWrapper(ProfileViewModel viewModel) =>
NotificationIcon(
count: viewModel.unreadCount.toString(),
onTap: () async => await viewModel.navigateToNotification(),
);
Widget _buildProfileSection(
@ -166,7 +166,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [
// _buildDownloadsCard(viewModel),
_buildProgressCard(viewModel),
_buildProgressCard(viewModel),
_buildAccountCard(viewModel),
_buildSupportCard(viewModel)
];

View File

@ -11,6 +11,7 @@ import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart';
@ -23,21 +24,26 @@ class ProfileViewModel extends ReactiveViewModel {
final _navigationService = locator<NavigationService>();
final _googleAuthService = locator<GoogleAuthService>();
final _imagePickerService = locator<ImagePickerService>();
final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_authenticationService];
[_authenticationService,_inAppNotificationService];
// Current user
User? get _user => _authenticationService.user;
User? get user => _user;
// Notification count
int get _unreadCount => _inAppNotificationService.unreadCount;
int get unreadCount => _unreadCount;
// Image picker
Future<void> openCamera() async =>
runBusyFuture(_openCamera(), busyObject: StateObjects.profileImage);
@ -80,21 +86,24 @@ class ProfileViewModel extends ReactiveViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToProfileDetail() async =>
await _navigationService.navigateToProfileDetailView();
Future<void> navigateToDownloads() async =>
await _navigationService.navigateToDownloadsView();
Future<void> navigateToSupport() async =>
await _navigationService.navigateToSupportView();
Future<void> navigateToProgress() async =>
await _navigationService.navigateToProgressView();
Future<void> navigateToDownloads() async =>
await _navigationService.navigateToDownloadsView();
Future<void> navigateToNotification() async =>
await _navigationService.navigateToNotificationView();
Future<void> navigateToProfileDetail() async =>
await _navigationService.navigateToProfileDetailView();
Future<void> navigateToAccountPrivacy() async =>
await _navigationService.navigateToAccountPrivacyView();
Future<void> navigateToSupport() async =>
await _navigationService.navigateToSupportView();
Future<void> navigateToLogin() async =>
await _navigationService.clearStackAndShow(Routes.loginView);

View File

@ -104,7 +104,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
viewModel.setSelectedOccupation(viewModel.occupations
.where((e) => (e.code ?? '') == viewModel.user?.occupation)
.first);
viewModel.setSelectedCountry(viewModel.countries
viewModel.setSelectedCountry(viewModel.countries
.where((e) => (e.code ?? '') == viewModel.user?.country)
.first);
if (viewModel.user?.country?.toLowerCase() == 'et') {

View File

@ -136,8 +136,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
void setEthiopianRegion(String region) {
_dropdownRegion = true;
_selectedRegion =
_regions.firstWhere((r) => r.code?.toLowerCase() == region.toLowerCase());
_selectedRegion = _regions
.firstWhere((r) => r.code?.toLowerCase() == region.toLowerCase());
rebuildUi();
}

View File

@ -17,7 +17,6 @@ class ProgressViewModel extends ReactiveViewModel {
@override
List<ListenableServiceMixin> get listenableServices => [_learnService];
// Total practice count
int get _totalCount => _learnService.totalCount;
@ -33,7 +32,6 @@ class ProgressViewModel extends ReactiveViewModel {
int get totalProgress => _totalProgress;
// Courses
final List<Map<String, dynamic>> _courses = [
{
@ -53,8 +51,9 @@ class ProgressViewModel extends ReactiveViewModel {
// Learning progress
Future<void> getProgressSummary() async => runBusyFuture(_getProgressSummary(),
busyObject: StateObjects.progressSummary);
Future<void> getProgressSummary() async =>
runBusyFuture(_getProgressSummary(),
busyObject: StateObjects.progressSummary);
Future<void> _getProgressSummary() async {
if (await _statusCheckerService.checkConnection()) {

View File

@ -10,7 +10,6 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../../../app/app.locator.dart';
import '../../../models/user.dart';
import '../../../services/apple_auth_service.dart';
import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart';
import '../../../services/status_checker_service.dart';
@ -26,23 +25,19 @@ class RegisterViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>();
final _appleAuthService = locator<AppleAuthService>();
final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_googleAuthService, _appleAuthService, _localizationService];
[_googleAuthService, _localizationService];
// Google user
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
GoogleSignInAccount? get googleUser => _googleUser;
bool get isAppleSignInAvailable => _appleAuthService.isSupported;
// Languages
Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage;
@ -342,50 +337,6 @@ class RegisterViewModel extends ReactiveViewModel
}
}
// Register with Apple
Future<void> registerWithApple() async => await runBusyFuture(_appleLogin(),
busyObject: StateObjects.registerWithApple);
Future<void> _appleLogin() 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> verifyOtp() async =>
await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp);

View File

@ -65,9 +65,7 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
Stack(
children: [
_buildScaffold(context: context, viewModel: viewModel),
_buildRegisterWithEmailState(viewModel),
_buildRegisterWithGoogleState(viewModel),
_buildRegisterWithAppleState(viewModel)
_buildRegisterWithEmailState(viewModel)
],
);
@ -193,8 +191,6 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
List<Widget> _buildLowerColumnChildren(RegisterViewModel viewModel) => [
_buildContinueButton(viewModel),
_buildRegisterWithGoogleButton(viewModel),
if (viewModel.isAppleSignInAvailable)
_buildRegisterWithAppleButton(viewModel),
_buildOptionTextDivider(),
_buildRegisterWithEmailButton(viewModel),
verticalSpaceMedium
@ -229,18 +225,6 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
onTap: () async => await viewModel.registerWithGoogle(),
);
Widget _buildRegisterWithAppleButton(RegisterViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
text: LocaleKeys.register_with_apple.tr(),
leadingIcon: Icons.apple,
onTap: () async => await viewModel.registerWithApple(),
);
Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildRegisterWithEmailButton(RegisterViewModel viewModel) =>
@ -259,14 +243,4 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
viewModel.busy(StateObjects.register)
? const PageLoadingIndicator()
: Container();
Widget _buildRegisterWithGoogleState(RegisterViewModel viewModel) =>
viewModel.busy(StateObjects.registerWithGoogle)
? const PageLoadingIndicator()
: Container();
Widget _buildRegisterWithAppleState(RegisterViewModel viewModel) =>
viewModel.busy(StateObjects.registerWithApple)
? const PageLoadingIndicator()
: Container();
}

View File

@ -0,0 +1,151 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/startup/startup_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
import '../../../common/translations/locale_keys.g.dart';
import '../../../widgets/custom_circular_progress_indicator.dart';
class FirstStartupScreen extends ViewModelWidget<StartupViewModel> {
final String? label;
const FirstStartupScreen({super.key,this.label});
@override
Widget build(BuildContext context, StartupViewModel viewModel) =>
_buildScaffoldWrapper();
Widget _buildScaffoldWrapper( ) => Scaffold(
backgroundColor: kcPrimaryColor,
body: _buildScaffoldPadding(),
);
Widget _buildScaffoldPadding( ) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildScaffold(),
);
Widget _buildScaffold( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildScaffoldChildren(),
);
List<Widget> _buildScaffoldChildren( ) =>
[_buildUpperColumn(), _buildLowerColumnWrapper()];
Widget _buildUpperColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(),
);
List<Widget> _buildUpperColumnChildren() =>
[verticalSpaceLarge, _buildIconWrapper(), verticalSpaceLarge];
Widget _buildIconWrapper() => Align(
alignment: Alignment.topLeft,
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo_white.svg',
height: 25,
);
Widget _buildLowerColumnWrapper( ) => Expanded(
child: _buildLowerColumn(),
);
Widget _buildLowerColumn( ) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildLowerColumnChildren(),
);
List<Widget> _buildLowerColumnChildren( ) => [
_buildTitle(),
verticalSpaceMedium,
_buildImageWrapper(),
verticalSpaceMedium,
_buildSafeWrapper()
];
Widget _buildTitle() => Text.rich(
TextSpan(
text: 'እንግሊዝኛ\n',
style: style25W600,
children: [
TextSpan(
text: 'በማንኛውም',
style: style25W400,
),
TextSpan(
text: ' ሰዓት ',
style: style25W600,
),
TextSpan(
text: 'ይማሩ!',
style: style25W400,
),
],
),
);
Widget _buildImageWrapper() => Expanded(child: _buildImageClipper());
Widget _buildImageClipper() => ClipRRect(
borderRadius: BorderRadius.circular(25),
child: _buildImage(),
);
Widget _buildImage() => Image.asset(
'assets/images/landing_1.png',
fit: BoxFit.cover,
);
Widget _buildSafeWrapper( ) =>
SafeArea(child: _buildContinueButtonWrapper());
Widget _buildContinueButtonWrapper( ) => Align(
alignment: Alignment.bottomCenter,
child: _buildLoadingTextContainer(),
);
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
}

View File

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

View File

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

View File

@ -1,9 +1,13 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/startup/screens/first_startup_screen.dart';
import 'package:yimaru_app/ui/views/startup/screens/second_startup_screen.dart';
import 'package:yimaru_app/ui/views/startup/screens/third_startup_screen.dart';
import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import '../../common/app_colors.dart';
@ -13,6 +17,7 @@ import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> {
final String? label;
const StartupView({Key? key, this.label}) : super(key: key);
@override
@ -24,83 +29,36 @@ class StartupView extends StackedView<StartupViewModel> {
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(StartupViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffoldState(viewModel),
backgroundColor: kcPrimaryColor,
body: _buildStartupScreens(viewModel),
);
Widget _buildScaffoldState(StartupViewModel viewModel) =>
viewModel.busy(StateObjects.startupView)
? _buildStartUpView()
: _buildScaffold();
Widget _buildStartUpView() =>
StartupView(label: LocaleKeys.checking_user_info.tr());
Widget _buildScaffold() => Stack(
children: _buildScaffoldChildren(),
Widget _buildStartupScreens(StartupViewModel viewModel) => FlutterCarousel(
options: FlutterCarouselOptions(
autoPlay: true,
viewportFraction: 1,
showIndicator: false,
height: double.maxFinite,
),
items: _buildScreens(),
);
List<Widget> _buildScaffoldChildren() => [
_buildBackground(),
_buildColumn(),
List<Widget> _buildScreens() => [
_buildFirstStartup(),
_buildSecondStartup(),
_buildThirdWelcome(),
];
Widget _buildBackground() => Image.asset(
'assets/images/loading.png',
fit: BoxFit.fill,
width: double.maxFinite,
height: double.maxFinite,
Widget _buildFirstStartup() => FirstStartupScreen(
label: label,
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildUpperColumnChildren(),
Widget _buildSecondStartup() => SecondStartupScreen(
label: label,
);
List<Widget> _buildUpperColumnChildren() =>
[_buildIconWrapper(), _buildSafeWrapper()];
Widget _buildSafeWrapper() => SafeArea(child: _buildLoadingTextContainer());
Widget _buildLoadingTextContainer() => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildLoadingTextWrapper(),
);
Widget _buildLoadingTextWrapper() => Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildLoadingTextChildren(),
);
List<Widget> _buildLoadingTextChildren() => [
_buildLoadingText(),
horizontalSpaceSmall,
_buildIndicatorWrapper(),
];
Widget _buildLoadingText() =>
Text('${label ?? LocaleKeys.loading.tr()} ...', style: style16W600);
Widget _buildIndicatorWrapper() => SizedBox(
width: 16,
height: 16,
child: _buildIndicator(),
);
Widget _buildIndicator() =>
const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 120),
child: _buildIcon(),
);
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg',
height: 50,
Widget _buildThirdWelcome() => ThirdStartupScreen(
label: label,
);
@override

View File

@ -8,6 +8,7 @@ import '../../../app/app.router.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/image_downloader_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/localization_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
@ -28,6 +29,7 @@ class StartupViewModel extends ReactiveViewModel {
final _imageDownloaderService = locator<ImageDownloaderService>();
@override
List<ListenableServiceMixin> get listenableServices =>
[_onboardingService, _authenticationService];
@ -133,8 +135,6 @@ class StartupViewModel extends ReactiveViewModel {
}
}
// Remote api call
// Onboarding fields
Future<void> getOnboardingFields() async {
bool response = await _onboardingService.getOnboardingFields();
@ -144,4 +144,6 @@ class StartupViewModel extends ReactiveViewModel {
await replaceWithFailure();
}
}
}

View File

@ -17,6 +17,7 @@ import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
final int index;
final bool last;
final bool first;
final LearnLesson lesson;
final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
@ -26,6 +27,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
this.onLessonTap,
this.onPracticeTap,
required this.last,
required this.first,
required this.index,
required this.lesson});
@ -35,7 +37,13 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildContainerWrapper(LearnLessonViewModel viewModel) =>
GestureDetector(
onTap: !(lesson.access?.isAccessible ?? false) ? onPracticeTap : null,
onTap: viewModel.user?.subscriptionStatus?.toLowerCase() == 'active'
? !(lesson.access?.isAccessible ?? false)
? onPracticeTap
: null
: !first
? onPracticeTap
: null,
child: _buildContainer(viewModel),
);
@ -56,7 +64,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildTileStack(LearnLessonViewModel viewModel) => Stack(
children: [
_buildExpansionTile(viewModel),
_buildContainerShaderState()
_buildContainerShaderState(viewModel)
],
);
@ -70,7 +78,6 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
leading: _buildLeadingWrapper(viewModel),
enabled: (lesson.access?.isAccessible ?? false),
controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
@ -85,6 +92,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
collapsedBackgroundColor: (lesson.access?.isCompleted ?? false)
? kcGreen.withOpacity(0.1)
: kcPrimaryColor.withOpacity(0.1),
enabled: first ? true : (lesson.access?.isAccessible ?? false),
children: _buildExpansionTileChildren(viewModel),
);
@ -203,9 +211,14 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildContainerShaderState() => !(lesson.access?.isAccessible ?? false)
? _buildContainerShader()
: Container();
Widget _buildContainerShaderState(LearnLessonViewModel viewModel) =>
viewModel.user?.subscriptionStatus?.toLowerCase() == 'active'
? !(lesson.access?.isAccessible ?? false)
? _buildContainerShader()
: Container()
: !first
? _buildContainerShader()
: Container();
Widget _buildContainerShader() => const CustomContainerShader();
}

View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/models/in_app_notification.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
class NotificationCard extends StatelessWidget {
final InAppNotification notification;
const NotificationCard({super.key, required this.notification});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
height: 100,
width: double.maxFinite,
margin: const EdgeInsets.symmetric(horizontal: 15),
padding: const EdgeInsets.symmetric(horizontal: 15,vertical: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: (notification.isRead ?? false)
? kcGreen.withOpacity(0.05)
: kcPrimaryColor.withOpacity(0.05),
border: Border.all(
color: (notification.isRead ?? false)
? kcGreen.withOpacity(0.1)
: kcPrimaryColor.withOpacity(0.1),
),
),
child: _buildRow(),
);
Widget _buildRow() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildRowChildren(),
);
List<Widget> _buildRowChildren()=> [
_buildIcon(),
horizontalSpaceSmall,
_buildColumnWrapper()
];
Widget _buildIcon() => const Icon(
Icons.notifications_none,
size: 35,
color: kcMediumGrey,
);
Widget _buildColumnWrapper()=> Expanded(child: _buildColumn());
Widget _buildColumn() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[ _buildTitle(), _buildSubtitle()];
Widget _buildTitle() => Text(
notification.payload?.headline ?? '',
style: style16DG600,
);
Widget _buildSubtitle() => Text(
notification.payload?.message ?? '',
maxLines: 2,
style: style14MG400,
);
}

View File

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

View File

@ -7,13 +7,16 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../common/app_colors.dart';
import '../common/translations/locale_keys.g.dart';
import 'notification_icon.dart';
class ProfileAppBar extends StatelessWidget {
final String? name;
final String unreadCount;
final String? profileImage;
final GestureTapCallback? onTap;
const ProfileAppBar(
{super.key, required this.name, required this.profileImage});
{super.key, this.onTap, required this.name,required this.unreadCount, required this.profileImage});
@override
Widget build(BuildContext context) => _buildStack();
@ -91,11 +94,8 @@ class ProfileAppBar extends StatelessWidget {
style: style14DG400,
);
Widget _buildNotificationIconWrapper() =>
Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon());
Widget _buildNotificationIcon() => const Icon(
Icons.notifications_none,
color: kcDarkGrey,
);
Widget _buildNotificationIconWrapper() =>
NotificationIcon(count: unreadCount,onTap:onTap ,)
;
}

View File

@ -17,7 +17,6 @@ import google_sign_in_ios
import package_info_plus
import record_macos
import shared_preferences_foundation
import sign_in_with_apple
import sqflite_darwin
import url_launcher_macos
import video_player_avfoundation
@ -36,7 +35,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

View File

@ -113,6 +113,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.3.0"
badges:
dependency: "direct main"
description:
name: badges
sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041
url: "https://pub.dev"
source: hosted
version: "3.2.0"
bloc:
dependency: transitive
description:
@ -1121,10 +1129,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.17.0"
mime:
dependency: transitive
description:
@ -1557,30 +1565,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
sign_in_with_apple:
dependency: "direct main"
description:
name: sign_in_with_apple
sha256: "5568378c3cc5993931955357d85e4c3344fa4365006915bdef965fa3de1dc0a5"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
sign_in_with_apple_platform_interface:
dependency: transitive
description:
name: sign_in_with_apple_platform_interface
sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
sign_in_with_apple_web:
dependency: transitive
description:
name: sign_in_with_apple_web
sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
@ -1742,10 +1726,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
version: "0.7.10"
timezone:
dependency: transitive
description:
@ -2035,5 +2019,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.41.0"
dart: ">=3.10.3 <4.0.0"
flutter: ">=3.38.4"

View File

@ -1,6 +1,6 @@
name: yimaru_app
publish_to: 'none'
version: 0.1.33+35
version: 0.1.41+43
description: A new Flutter project.
environment:
@ -16,6 +16,7 @@ dependencies:
path: ^1.9.1
async: ^2.13.1
pinput: ^6.0.1
badges: ^3.2.0
stacked: ^3.4.0
iconsax: ^0.0.8
chewie: ^1.13.0
@ -55,7 +56,6 @@ dependencies:
flutter_phone_direct_caller: ^2.2.1
flutter_local_notifications: ^20.1.0
internet_connection_checker_plus: ^2.9.1+2
sign_in_with_apple: ^8.0.0
dev_dependencies:
flutter_test:

View File

@ -11,7 +11,6 @@ import 'package:yimaru_app/services/permission_handler_service.dart';
import 'package:yimaru_app/services/image_picker_service.dart';
import 'package:yimaru_app/services/google_auth_service.dart';
import 'package:yimaru_app/services/image_downloader_service.dart';
import 'package:yimaru_app/services/notification_service.dart';
import 'package:yimaru_app/services/smart_auth_service.dart';
import 'package:yimaru_app/services/course_service.dart';
import 'package:yimaru_app/services/audio_player_service.dart';
@ -23,6 +22,8 @@ import 'package:yimaru_app/services/phone_caller_service.dart';
import 'package:yimaru_app/services/learn_service.dart';
import 'package:yimaru_app/services/localization_service.dart';
import 'package:yimaru_app/services/onboarding_service.dart';
import 'package:yimaru_app/services/in_app_notification_service.dart';
import 'package:yimaru_app/services/push_notification_service.dart';
// @stacked-import
@GenerateMocks(
@ -57,6 +58,10 @@ import 'package:yimaru_app/services/onboarding_service.dart';
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<OnboardingService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<InAppNotificationService>(
onMissingStub: OnMissingStub.returnDefault),
MockSpec<PushNotificationService>(
onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec
],
)
@ -88,6 +93,8 @@ void registerServices() {
getAndRegisterLearnService();
getAndRegisterLocalizationService();
getAndRegisterOnboardingService();
getAndRegisterInAppNotificationService();
getAndRegisterPushNotificationService();
// @stacked-mock-register
}
@ -208,13 +215,6 @@ MockImageDownloaderService getAndRegisterImageDownloaderService() {
return service;
}
MockNotificationService getAndRegisterNotificationService() {
_removeRegistrationIfExists<NotificationService>();
final service = MockNotificationService();
locator.registerSingleton<NotificationService>(service);
return service;
}
MockSmartAuthService getAndRegisterSmartAuthService() {
_removeRegistrationIfExists<SmartAuthService>();
final service = MockSmartAuthService();
@ -291,6 +291,20 @@ MockOnboardingService getAndRegisterOnboardingService() {
locator.registerSingleton<OnboardingService>(service);
return service;
}
MockInAppNotificationService getAndRegisterInAppNotificationService() {
_removeRegistrationIfExists<InAppNotificationService>();
final service = MockInAppNotificationService();
locator.registerSingleton<InAppNotificationService>(service);
return service;
}
MockPushNotificationService getAndRegisterPushNotificationService() {
_removeRegistrationIfExists<PushNotificationService>();
final service = MockPushNotificationService();
locator.registerSingleton<PushNotificationService>(service);
return service;
}
// @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() {

View File

@ -0,0 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('InAppNotificationServiceTest -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('NotificationServiceTest -', () {
group('PushNotificationServiceTest -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});

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