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

View File

@ -9,14 +9,12 @@
"cont": "Continue", "cont": "Continue",
"register": "Register", "register": "Register",
"login_with_google": "Login with Google", "login_with_google": "Login with Google",
"login_with_apple": "Login with Apple",
"or": "Or", "or": "Or",
"login_with_phone": "Login with phone number", "login_with_phone": "Login with phone number",
"create_account": "Create an account", "create_account": "Create an account",
"already_have_account": "Already have an account?", "already_have_account": "Already have an account?",
"login": "Login", "login": "Login",
"register_with_google": "Register with Google", "register_with_google": "Register with Google",
"register_with_apple": "Register with Apple",
"register_with_phone": "Register with phone number", "register_with_phone": "Register with phone number",
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.", "enter_phone_number": "Enter your phone number. We will send you a confirmation code there.",
"login_with_email": "Login with email", "login_with_email": "Login with email",
@ -192,8 +190,9 @@
"finish_all_practice_previouse_course": "Finish the previous course practice to take this", "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.", "track_journey": "Track your learning journey and see your growth over time.",
"learn_english": "Learn English", "learn_english": "Learn English",
"keep_momentum": "Great job! Keep the momentum.", "keep_momentum":"Great job! Keep the momentum.",
"completed_practices": "Completed Practices", "completed_practices": "Completed Practices",
"total_practices": "Total Practices", "total_practices": "Total Practices",
"progress_percentage": "Progress Percentage" "progress_percentage": "Progress Percentage",
"notifications": "Notifications"
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,4 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </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 import UIKit
@main @main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { @objc class AppDelegate: FlutterAppDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} }

View File

@ -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"?> <?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"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict>
<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> <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/> <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> </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> </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/course_payment/course_payment_view.dart';
import 'package:yimaru_app/ui/views/failure/failure_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/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/ui/views/duolingo/duolingo_view.dart';
import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/smart_auth_service.dart';
import 'package:yimaru_app/services/course_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/landing/landing_view.dart';
import 'package:yimaru_app/ui/views/course_module/course_module_view.dart'; import 'package:yimaru_app/ui/views/course_module/course_module_view.dart';
import 'package:yimaru_app/services/onboarding_service.dart'; import 'package:yimaru_app/services/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/learn_course/learn_course_view.dart';
import 'package:yimaru_app/ui/views/payment/payment_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 // @stacked-import
@StackedApp( @StackedApp(
@ -98,6 +99,7 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
MaterialRoute(page: CourseModuleView), MaterialRoute(page: CourseModuleView),
MaterialRoute(page: LearnCourseView), MaterialRoute(page: LearnCourseView),
MaterialRoute(page: PaymentView), MaterialRoute(page: PaymentView),
MaterialRoute(page: NotificationView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [
@ -113,7 +115,6 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
LazySingleton(classType: ImagePickerService), LazySingleton(classType: ImagePickerService),
LazySingleton(classType: GoogleAuthService), LazySingleton(classType: GoogleAuthService),
LazySingleton(classType: ImageDownloaderService), LazySingleton(classType: ImageDownloaderService),
LazySingleton(classType: NotificationService),
LazySingleton(classType: SmartAuthService), LazySingleton(classType: SmartAuthService),
LazySingleton(classType: CourseService), LazySingleton(classType: CourseService),
LazySingleton(classType: AudioPlayerService), LazySingleton(classType: AudioPlayerService),
@ -125,7 +126,8 @@ import 'package:yimaru_app/ui/views/payment/payment_view.dart';
LazySingleton(classType: LearnService), LazySingleton(classType: LearnService),
LazySingleton(classType: LocalizationService), LazySingleton(classType: LocalizationService),
LazySingleton(classType: OnboardingService), LazySingleton(classType: OnboardingService),
LazySingleton(classType: AppleAuthService), LazySingleton(classType: InAppNotificationService),
LazySingleton(classType: PushNotificationService),
// @stacked-service // @stacked-service
], ],
bottomsheets: [ bottomsheets: [

View File

@ -13,7 +13,6 @@ import 'package:stacked_services/src/navigation/navigation_service.dart';
import 'package:stacked_shared/stacked_shared.dart'; import 'package:stacked_shared/stacked_shared.dart';
import '../services/api_service.dart'; import '../services/api_service.dart';
import '../services/apple_auth_service.dart';
import '../services/audio_player_service.dart'; import '../services/audio_player_service.dart';
import '../services/authentication_service.dart'; import '../services/authentication_service.dart';
import '../services/course_service.dart'; import '../services/course_service.dart';
@ -21,13 +20,14 @@ import '../services/dio_service.dart';
import '../services/google_auth_service.dart'; import '../services/google_auth_service.dart';
import '../services/image_downloader_service.dart'; import '../services/image_downloader_service.dart';
import '../services/image_picker_service.dart'; import '../services/image_picker_service.dart';
import '../services/in_app_notification_service.dart';
import '../services/in_app_update_service.dart'; import '../services/in_app_update_service.dart';
import '../services/learn_service.dart'; import '../services/learn_service.dart';
import '../services/localization_service.dart'; import '../services/localization_service.dart';
import '../services/notification_service.dart';
import '../services/onboarding_service.dart'; import '../services/onboarding_service.dart';
import '../services/permission_handler_service.dart'; import '../services/permission_handler_service.dart';
import '../services/phone_caller_service.dart'; import '../services/phone_caller_service.dart';
import '../services/push_notification_service.dart';
import '../services/secure_storage_service.dart'; import '../services/secure_storage_service.dart';
import '../services/smart_auth_service.dart'; import '../services/smart_auth_service.dart';
import '../services/status_checker_service.dart'; import '../services/status_checker_service.dart';
@ -56,7 +56,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => ImagePickerService()); locator.registerLazySingleton(() => ImagePickerService());
locator.registerLazySingleton(() => GoogleAuthService()); locator.registerLazySingleton(() => GoogleAuthService());
locator.registerLazySingleton(() => ImageDownloaderService()); locator.registerLazySingleton(() => ImageDownloaderService());
locator.registerLazySingleton(() => NotificationService());
locator.registerLazySingleton(() => SmartAuthService()); locator.registerLazySingleton(() => SmartAuthService());
locator.registerLazySingleton(() => CourseService()); locator.registerLazySingleton(() => CourseService());
locator.registerLazySingleton(() => AudioPlayerService()); locator.registerLazySingleton(() => AudioPlayerService());
@ -68,5 +67,6 @@ Future<void> setupLocator(
locator.registerLazySingleton(() => LearnService()); locator.registerLazySingleton(() => LearnService());
locator.registerLazySingleton(() => LocalizationService()); locator.registerLazySingleton(() => LocalizationService());
locator.registerLazySingleton(() => OnboardingService()); 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( static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk', apiKey: 'AIzaSyDbaGD47oUJOyn9n3b0pbH6ozmbGyIlOKk',
appId: '1:900714037062:ios:45b484d79222c3ab4e6f47', appId: '1:900714037062:ios:1caf8f24a4333b8e4e6f47',
messagingSenderId: '900714037062', messagingSenderId: '900714037062',
projectId: 'yimaru-academy-5e7e2', projectId: 'yimaru-academy-5e7e2',
storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app', storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app',
androidClientId: androidClientId:
'900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com', '900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com',
iosClientId: iosClientId:
'900714037062-3qqf7urii8vg99id91nmmuvom3fm5c1u.apps.googleusercontent.com', '900714037062-35bg0hsou56hg37mbcbpiar9uti7tcku.apps.googleusercontent.com',
iosBundleId: 'com.yimaru.lms.testapp', 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.locator.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import 'package:stacked_services/stacked_services.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: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 'package:yimaru_app/ui/common/translations/codegen_loader.g.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
@ -17,7 +17,7 @@ Future<void> main() async {
await setupLocator(); await setupLocator();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await locator<NotificationService>().initialize(); await locator<PushNotificationService>().initialize();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
setupDialogUi(); setupDialogUi();
setupBottomSheetUi(); 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:dio/dio.dart';
import 'package:yimaru_app/models/app_update.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_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart'; import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/learn_program.dart';
@ -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 // Verify otp
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async { Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
try { 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 // Get assessment question sets
Future<List<Assessment>> getAssessments() async { Future<List<Assessment>> getAssessments() async {
try { 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 // Initialization
AuthenticationService() { AuthenticationService() {
listenToReactiveValues([_user,_state, _localizationService]); listenToReactiveValues([_user, _state, _localizationService]);
} }
// Logout state // Logout state
@ -31,7 +31,6 @@ class AuthenticationService with ListenableServiceMixin {
StateObjects get state => _state; StateObjects get state => _state;
// Check user logged in // Check user logged in
Future<bool> userLoggedIn() async { Future<bool> userLoggedIn() async {
if (await _secureService.getString('userId') != null) { if (await _secureService.getString('userId') != null) {
@ -202,7 +201,6 @@ class AuthenticationService with ListenableServiceMixin {
await setFirstTimeInstall(firstTimeInstall); await setFirstTimeInstall(firstTimeInstall);
await _secureService.setString('language', language); await _secureService.setString('language', language);
_state = StateObjects.none; _state = StateObjects.none;
notifyListeners(); notifyListeners();
} }

View File

@ -29,7 +29,6 @@ class GoogleAuthService with ListenableServiceMixin {
_googleUser ??= _googleUser ??=
await _signIn.authenticate(scopeHint: ['email', 'profile']); await _signIn.authenticate(scopeHint: ['email', 'profile']);
}); });
notifyListeners(); notifyListeners();
} catch (e) { } 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; List<LearnLesson> get lessons => _lessons;
// Learn programs // Learn programs
Future<String?> refreshObject(String url) async { Future<String?> refreshObject(String url) async {
Map<String, dynamic> data = {'reference': url}; 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 kFilesUrl = 'files';
String kUnreadUrl = 'unread';
String kApiVersionUrl = 'v1'; String kApiVersionUrl = 'v1';
String kLevelsUrl = 'levels'; String kLevelsUrl = 'levels';
String kCoursesUrl = 'courses'; String kCoursesUrl = 'courses';
String kModulesUrl = 'modules'; String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons'; String kLessonsUrl = 'lessons';
@ -69,6 +72,8 @@ String kQuestionSetsUrl = 'question-sets';
String kRequestResetCode = 'sendResetCode'; String kRequestResetCode = 'sendResetCode';
String kNotificationsUrl = 'notifications';
String kSubcategoriesUrl = 'sub-categories'; String kSubcategoriesUrl = 'sub-categories';
String kProgressSummary = 'progress-summary'; String kProgressSummary = 'progress-summary';
@ -79,6 +84,8 @@ String kCoursePracticeQuestions = 'questions';
String kCatalogCoursesUrl = 'catalog-courses'; String kCatalogCoursesUrl = 'catalog-courses';
String kMarkNotificationRead = 'mark-all-read';
String kUpdateProfileImage = 'profile-picture'; String kUpdateProfileImage = 'profile-picture';
String kSubscriptionsUrl = 'subscription-plans'; String kSubscriptionsUrl = 'subscription-plans';
@ -101,8 +108,6 @@ String kLessonProgressUrl = 'api/v1/progress/videos';
String kGoogleAuthUrl = 'api/v1/auth/google/android'; String kGoogleAuthUrl = 'api/v1/auth/google/android';
String kAppleAuthUrl = 'api/v1/auth/apple';
String kCourseProgressUrl = 'api/v1/progress/courses'; String kCourseProgressUrl = 'api/v1/progress/courses';
String kAssessmentsUrl = 'api/v1/assessment/questions'; String kAssessmentsUrl = 'api/v1/assessment/questions';

View File

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

View File

@ -6,7 +6,7 @@ import 'dart:ui';
import 'package:easy_localization/easy_localization.dart' show AssetLoader; import 'package:easy_localization/easy_localization.dart' show AssetLoader;
class CodegenLoader extends AssetLoader{ class CodegenLoader extends AssetLoader {
const CodegenLoader(); const CodegenLoader();
@override @override
@ -14,403 +14,431 @@ class CodegenLoader extends AssetLoader{
return Future.value(mapLocales[locale.toString()]); return Future.value(mapLocales[locale.toString()]);
} }
static const Map<String,dynamic> _en = { static const Map<String, dynamic> _am = {
"loading": "Loading", "loading": "በመጫን ላይ",
"welcome_back": "Welcome back", "welcome_back": "እንኳን በደህና ተመለሱ",
"checking_user_info": "Checking user info", "checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ",
"dont_have_account": "Don't have an account?", "dont_have_account": "መለያ የለዎትም?",
"email": "Email", "email": "ኢሜይል",
"password": "Password", "password": "የይለፍ ቃል",
"forgot_password": "Forgot password?", "forgot_password": "የይለፍ ቃል ረሱ?",
"cont": "Continue", "cont": "ቀጥል",
"register": "Register", "register": "ይመዝገቡ",
"login_with_google": "Login with Google", "login_with_google": "በጉግል ይግቡ",
"login_with_apple": "Login with Apple", "or": "ወይም",
"or": "Or", "login_with_phone": "በስልክ ቁጥር ይግቡ",
"login_with_phone": "Login with phone number", "create_account": "አዲስ መለያ ይፍጠሩ",
"create_account": "Create an account", "already_have_account": "መለያ አለዎት?",
"already_have_account": "Already have an account?", "login": " ይግቡ ",
"login": "Login", "register_with_google": "በጉግል ይመዝገቡ",
"register_with_google": "Register with Google", "register_with_phone": "በስልክ ቁጥር ይመዝገቡ",
"register_with_apple": "Register with Apple", "enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።",
"register_with_phone": "Register with phone number", "login_with_email": "በኢሜይል ይግቡ",
"enter_phone_number": "Enter your phone number. We will send you a confirmation code there.", "create_password": "የይለፍ ቃል ይፍጠሩ",
"login_with_email": "Login with email", "confirm_password": "የይለፍ ቃል ያረጋግጡ",
"create_password": "Create password", "eight_character_minimum": "ቢያንስ 8 ፊደላት",
"confirm_password": "Confirm password", "password_match": "የይለፍ ቃሉ ተመሳስሏል",
"eight_character_minimum": "8 characters minimum", "sign_up_agreement":
"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_services": "Terms of Service", "and": "እና",
"and": "and", "privacy_policy": "የግላዊነት ፖሊሲ",
"privacy_policy": "Privacy Policy", "register_with_email": "በኢሜል ይመዝገቡ",
"register_with_email": "Register with email", "verification_code": "የማረጋገጫ ኮድ",
"verification_code": "Verification Code", "resend_code": "ኮዱን እንደገና ላክ",
"resend_code": "Resend Code", "code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል",
"code_sent_to_phone": "Code sent to your number", "code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል",
"code_sent_to_email": "Code sent to your email", "resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ",
"resend_code_in": "Resend code in", "reset_password": " የይለፍ ቃልን ይቀይሩ",
"reset_password": "Reset Password", "enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።",
"enter_email_reset_code": "Enter your email. We will send you a reset code.", "please_wait": "እባክዎ ይጠብቁ",
"please_wait": "Please wait", "reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል",
"reset_code_sent": "Reset code sent successfully", "reset_code": " የመቀየሪያ ኮድ ",
"reset_code": "Reset code", "new_password": "አዲስ የይለፍ ቃል",
"new_password": "New password", "logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል",
"logged_in_successfully": "Logged in successfully", "view_course": " ኮርሱን ይመልከቱ",
"continue_learning": "Continue Learning", "continue_learning": "መማርን ይቀጥሉ",
"start_learning": "Start Learning", "start_learning": "ትምህርትን ይጀምሩ",
"completed": "Completed", "completed": "ተጠናቋል",
"view_course": "View course", "take_practice": "ልምምድ ያድርጉ",
"take_practice": "Take practice", "your_current_level": "የአሁኑ ደረጃዎ",
"your_current_level": "Your current level", "overall_progress": "አጠቃላይ እድገት",
"overall_progress": "Overall progress", "great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው",
"great_work": "Keep up the great work! You're doing amazing", "view_module": "ሞጁሉን ይመልከቱ",
"view_module": "View module", "progress": "እድገት",
"progress": "Progress", "keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ",
"keep_going": "Let's keep going - you're more than half there", "lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ",
"lessons_in_module": "Lessons in this module", "practice": "ልምምድ",
"practice": "Practice", "start": "ጀምር",
"start": "Start", "in_progress": "በሂደት ላይ",
"in_progress": "In Progress", "hello": "ሰላም",
"hello": "Hello", "ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ",
"ready_to_learn": "Ready to keep learning English today", "learn": "ይማሩ ",
"learn": "Learn", "course": "ኮርስ",
"course": "Course", "profile": " ፕሮፋይል ",
"profile": "Profile", "speaking_partner": "የንግግር ጓደኛ",
"speaking_partner": "Speaking partner", "practice_what_you_learned": "አሁን የተማሩትን እንለማመድ",
"practice_what_you_learned": "Let's practice what you just learnt", "practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ",
"practice_questions": "I will ask you a few questions and you can respond", "start_practice": "ልምምድ ጀምር",
"start_practice": "Start practice", "almost_there": "ሊጨርሱ ተቃርበዋል",
"almost_there": "You're almost there", "finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ",
"finish_session": "Finish the session to see your progress", "continue_practice": "ልምምዱን ይቀጥሉ",
"continue_practice": "Continue practice", "end_session": "ክፍለ ጊዜውን ያብቁ ",
"end_session": "End session", "tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ",
"tap_start_to_listen": "Tap the start button to listen", "practice_speaking": "ንግግርን ይለማመዱ",
"practice_speaking": "Practice speaking", "tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ",
"tap_microphone": "Tap the microphone to speak", "reply": "እንደገና አዳምጥ",
"reply": "Reply", "cancel": "ይቅር",
"cancel": "Cancel", "you_are_speaking": "እየተናገሩ ነው",
"you_are_speaking": "You're speaking", "practice_completed": "ልምምዱ ተጠናቅቋል",
"practice_completed": "Practice completed!", "great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው",
"great_improvement": "You sound more confident this time, great improvement", "practice_again": "እንደገና ይለማመዱ",
"practice_again": "Practice again", "conversation_review": "የንግግር ግምገማ",
"conversation_review": "Conversation review", "result": "ውጤት",
"result": "Result", "quick_tip": "ጠቃሚ ምክር",
"quick_tip": "Quick tip", "retry": "እንደገና ይሞክሩ",
"retry": "Retry", "completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል",
"completed_a1": "Yay, you've completed A1", "analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው",
"analyzing_speaking": "We're now analyzing your speaking skill", "view_profile": "ፕሮፋይሎን ይመልከቱ ",
"view_profile": "View profile", "hi": "ሰላም",
"hi": "Hi", "edit_profile": "መገለጫ ያስተካክሉ",
"edit_profile": "Edit profile", "first_name": "የመጀመሪያ ስም",
"first_name": "First name", "last_name": "የአባት ስም",
"last_name": "Last name", "gender": "ፆታ",
"gender": "Gender", "male": "ወንድ",
"male": "Male", "female": "ሴት",
"female": "Female", "phone_number": "የስልክ ቁጥር",
"phone_number": "Phone number", "country": "ሀገር",
"country": "Country", "region": "ክልል",
"region": "Region", "select_region": "ክልል ይምረጡ",
"select_region": "Select region", "enter_your_city": "ከተማዎን ያስገቡ",
"enter_your_city": "Enter your city", "occupation": "የስራ መስክ",
"occupation": "Occupation", "select_occupation": "ሙያዎን ይምረጡ",
"select_occupation": "Select occupation", "save_changes": "ለውጦችን ያስቀምጡ",
"save_changes": "Save changes", "my_progress": "የእኔ እድገት",
"my_progress": "My progress", "track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ",
"track_your_achievement": "Track your achievements and learning streak", "account_and_privacy": "መለያ እና ግላዊነት",
"account_and_privacy": "Account & Privacy", "manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ",
"manage_settings": "Manage settings and app preference", "support": "ድጋፍ",
"support": "Support", "get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ",
"get_help": "Get help through phone or Telegram", "logout": "ውጣ",
"logout": "Logout", "app_settings": "የመተግበሪያ ቅንብሮች",
"app_settings": "App settings", "legal_and_information": "ሕጋዊ እና መረጃ",
"legal_and_information": "Legal & Information", "change_language": "ቋንቋ ቀይር",
"change_language": "Change language", "terms_and_conditions": "ውሎች እና ሁኔታዎች",
"terms_and_conditions": "Terms & Conditions", "delete_account": "መለያ ሰርዝ",
"delete_account": "Delete account", "language_preference": "የቋንቋ ምርጫ",
"language_preference": "Language preference", "choose_your_language": "ለውጦችን አስቀምጥ",
"choose_your_language": "Choose your language", "switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ",
"switch_language_anytime": "You can switch languages anytime", "need_help": "እገዛ ይፈልጋሉ?",
"need_help": "Need help?", "call_support": "የስልክ ድጋፍ",
"call_support": "Call support", "talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ",
"talk_with_support": "Talk with our support team directly", "telegram_support": "የቴሌግራም ድጋፍ",
"telegram_support": "Telegram support", "chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ",
"chat_via_telegram": "Chat instantly via Telegram", "call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ",
"call_our_support": "Call our support team between 9 AM - 6 PM", "tap_to_call": "ለመደወል ይንኩ",
"tap_to_call": "Tap to call", "join_telegram": "በቴሌግራም የይማሩ አካዳሚን ይቀላቀሉ",
"join_telegram": "Join Yimaru Academy on Telegram", "connect_with_support_team":
"connect_with_support_team": "Connect with our support team instantly on Telegram for quick assistance and community updates", "ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።",
"open_in_telegram": "Open in Telegram", "open_in_telegram": "በቴሌግራም ይክፈቱ",
"search_for": "Search for", "search_for": "ፈልጉት",
"current_level": "Current Level", "current_level": "የአሁኑ ደረጃ",
"keep_up_the_great_work": "Keep up the great work! You're doing amazing.", "keep_up_the_great_work": "በጣም ጥሩ እየሰራህ ነው! ቀጥልበት፣ አስደናቂ ነህ።",
"no_practice_available": "No practice available!", "no_practice_available": "ምንም ልምምድ አልተገኘም!",
"begin_module_practice": "Begin Module Practice", "begin_module_practice": "የሞጁሉን ልምምድ ጀምር",
"lets_practice_lesson": "Lets Practice", "lets_practice_lesson": "እንለማመድ",
"lets_quickly_review": "Lets quickly review what youve learned in this module!", "lets_quickly_review": "በዚህ ሞጁል ውስጥ የተማርከውን በፍጥነት እንከልስ!",
"lets_practice_module": "Let's practice what you just learnt!", "lets_practice_module": "አሁን የተማርከውን እንለማመድ!",
"ask_you_few_actions": "Ill ask you a few questions, and you can respond naturally.", "ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።",
"begin_level_practice": "Begin Level Practice", "begin_level_practice": "የደረጃ ልምምድን ጀምር",
"lets_practice_course": "Lets Practice Course", "lets_practice_course": "የኮርሱን ልምምድ እንለማመድ",
"lets_quick_review": "Lets quickly review what youve learned in this level!", "lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!",
"speaking": "is speaking...", "speaking": "እየተናገረ ነው",
"you_have_finished_practice": "You have finished your practice", "you_have_finished_practice": "ልምምድህን አጠናቀቅህ",
"view_results": "View My Results", "view_results": "ውጤቶቼን እይ",
"sample_answer": "Sample Answer", "sample_answer": "ናሙና መልስ",
"your_answer": "Your Answer", "your_answer": "መልስህ",
"sound_confident": "You sound more confident this time - great improvement!", "sound_confident": "በዚህ ጊዜ የበለጠ እምነት ያለህ ይመስላል — በጣም ጥሩ መሻሻል ነው!",
"you_have_completed": "Yay, youve completed", "you_have_completed": "አያይ! አጠናቀቅህ",
"yes": "Yes", "yes": "አዎ",
"no": "No", "no": "አይ",
"want_to_quit": "Are you sure you want to quit?", "want_to_quit": "ለመውጣት እርግጠኛ ነህ?",
"required_field": "The field is required", "required_field": "ይህ መስክ ያስፈልጋል",
"enter_full_name": "Enter your full name", "enter_full_name": "ሙሉ ስምህን አስገባ",
"invalid_email": "Invalid email format", "invalid_email": "የማይሰራ የኢሜይል ቅርጸት",
"phone_must_start_with": "Phone number must start with 251", "phone_must_start_with": "የስልክ ቁጥር በ251 መጀመር አለበት",
"phone_must_be": "Phone number must be 12 digits", "phone_must_be": "የስልክ ቁጥር 12 አሃዞች መሆን አለበት",
"what_should_we_call_you": "What should we call you?", "what_should_we_call_you": "ምን ብለን እንጠራህ?",
"name_for_personalization": "Well use your name to personalize your learning journey.", "name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።",
"choose_your_gender": "Choose your gender?", "choose_your_gender": "ጾታህን ምረጥ",
"gender_for_personalization": "Well personalize your learning experience based on your gender.", "gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"age_range": "Which age range are you in?", "age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?",
"age_for_personalization": "Well personalize your learning experience based on your age.", "age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"educational_background": "Whats your current educational level?", "educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?",
"education_for_personalization": "This helps us tailor your lessons to your experience.", "education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።",
"your_occupation": "Whats your occupation?", "your_occupation": "ስራህ ምንድን ነው?",
"occupation_for_personalization": "Well personalize your learning experience based on your occupation.", "occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።",
"location": "Where are you from?", "location": "ከየት ነህ?",
"select_country_region": "Select your country and region from the dropdown", "select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ",
"select_country": "Select country", "select_country": "አገር ምረጥ",
"learning_goal": "Choose your learning goal.", "learning_goal": "የመማር ዓላማህን ምረጥ",
"language_goal": "Whats your main goal for improving your English?", "language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?",
"your_goal": "Your goal helps us tailor your learning journey.", "your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።",
"write_your_goal": "Write your goal…", "write_your_goal": "ዓላማህን ጻፍ…",
"challenge_you_face": "What challenge do you face most with English?", "challenge_you_face": "What challenge do you face most with English?",
"evey_one_has_strugle": "Everyone has struggles, lets start fixing yours", "evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ",
"write_your_challenge": "Write your challenge…", "write_your_challenge": "ችግርህን ጻፍ…",
"topic_interest": "Which topics interest you most?", "topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?",
"favourite_topic": "Your favorite topics help us create fun, relatable lessons.", "favourite_topic":
"your_interest": "Write your interest…", "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።",
"want_quick_assessment": "Want a quick assessment to know your English level?", "your_interest": "ፍላጎትህን ጻፍ…",
"answer_quick_questions": "Answer a few quick questions to help us understand your English proficiency.", "want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?",
"skip": "Skip", "answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።",
"finish_level": "Finish Level", "skip": "ዝለል",
"likely_speaker": "Youre likely speaker of", "finish_level": "ደረጃውን አጠናቅቅ",
"great_job": "Great Job! Heres your next step to keep improving.", "likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ",
"lets_start_practice": "Let's start your practice", "great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።",
"welcome_abroad": "Welcome aboard", "lets_start_practice": "ልምምድህን እንጀምር",
"ready_to_explore": "Youre ready to explore your personalized lessons.", "welcome_abroad": "እንኳን ደህና መጣህ",
"finish": "Finish", "ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።",
"finish_all_practice_lesson": "Finish the previous lesson practice to take this practice", "finish": "አጠናቅቅ",
"finish_all_practice_module": "Finish the lesson practices to take the Module Practice", "finish_all_practice_lesson": "ይህን ልምምድ ለመውሰድ የቀድሞውን የትምህርት ልምምድ ያጠናቅቁ",
"finish_all_practice_course": "Finish the Module practices to take the Course practice", "finish_all_practice_module": "የሞጁሉን ልምምድ ለመውሰድ የትምህርት ልምምዶችን ያጠናቅቁ",
"finish_all_practice_previouse_module": "Finish the previous Module practice to take this practice", "finish_all_practice_course": "የኮርሱን ልምምድ ለመውሰድ የሞጁል ልምምዶችን ያጠናቅቁ",
"finish_all_practice_previouse_course": "Finish the previous course practice to take this", "finish_all_practice_previouse_module":
"track_journey": "Track your learning journey and see your growth over time.", "ይህን ልምምድ ለመውሰድ የቀድሞውን የሞጁል ልምምድ ያጠናቅቁ",
"learn_english": "Learn English", "finish_all_practice_previouse_course": "ይህን ለመውሰድ የቀድሞውን የኮርስ ልምምድ ያጠናቅቁ",
"keep_momentum": "Great job! Keep the momentum.", "track_journey": "የትምህርት ጉዞዎን ይከታተሉ እና በጊዜ ሂደት ያሳዩትን እድገት ይመልከቱ።",
"completed_practices": "Completed Practices", "learn_english": "እንግሊዝኛ ይማሩ",
"total_practices": "Total Practices", "keep_momentum": "በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።",
"progress_percentage": "Progress Percentage" "completed_practices": "የተጠናቀቁ ልምምዶች",
}; "total_practices": "ጠቅላላ ልምምዶች",
static const Map<String,dynamic> _am = { "progress_percentage": "የእድገት መቶኛ",
"loading": "በመጫን ላይ", "notifications": "ማሳወቂያዎች"
"welcome_back": "እንኳን በደህና ተመለሱ", };
"checking_user_info": "የተጠቃሚ መረጃን በማረጋገጥ ላይ", static const Map<String, dynamic> _en = {
"dont_have_account": "መለያ የለዎትም?", "loading": "Loading",
"email": "ኢሜይል", "welcome_back": "Welcome back",
"password": "የይለፍ ቃል", "checking_user_info": "Checking user info",
"forgot_password": "የይለፍ ቃል ረሱ?", "dont_have_account": "Don't have an account?",
"cont": "ቀጥል", "email": "Email",
"register": "ይመዝገቡ", "password": "Password",
"login_with_google": "በጉግል ይግቡ", "forgot_password": "Forgot password?",
"login_with_apple": "በአፕል ይግቡ", "cont": "Continue",
"or": "ወይም", "register": "Register",
"login_with_phone": "በስልክ ቁጥር ይግቡ", "login_with_google": "Login with Google",
"create_account": "አዲስ መለያ ይፍጠሩ", "or": "Or",
"already_have_account": "መለያ አለዎት?", "login_with_phone": "Login with phone number",
"login": " ይግቡ ", "create_account": "Create an account",
"register_with_google": "በጉግል ይመዝገቡ", "already_have_account": "Already have an account?",
"register_with_apple": "በአፕል ይመዝገቡ", "login": "Login",
"register_with_phone": "በስልክ ቁጥር ይመዝገቡ", "register_with_google": "Register with Google",
"enter_phone_number": "የስልክ ቁጥርዎን ያስገቡ። የማረጋገጫ ኮድ እንልክልዎታለን።", "register_with_phone": "Register with phone number",
"login_with_email": "በኢሜይል ይግቡ", "enter_phone_number":
"create_password": "የይለፍ ቃል ይፍጠሩ", "Enter your phone number. We will send you a confirmation code there.",
"confirm_password": "የይለፍ ቃል ያረጋግጡ", "login_with_email": "Login with email",
"eight_character_minimum": "ቢያንስ 8 ፊደላት", "create_password": "Create password",
"password_match": "የይለፍ ቃሉ ተመሳስሏል", "confirm_password": "Confirm password",
"sign_up_agreement": "‘ይመዝገቡ’ የሚለውን ሲጫኑ በ‘አገልግሎት ውሎች’ እና ‘በግላዊነት ፖሊሲ’ ይስማማሉ።", "eight_character_minimum": "8 characters minimum",
"terms_of_services": "የአገልግሎት ውሎች", "password_match": "password match",
"and": "እና", "sign_up_agreement":
"privacy_policy": "የግላዊነት ፖሊሲ", "By clicking Sign Up, you agree to our Terms of Service and Privacy Policy",
"register_with_email": "በኢሜል ይመዝገቡ", "terms_of_services": "Terms of Service",
"verification_code": "የማረጋገጫ ኮድ", "and": "and",
"resend_code": "ኮዱን እንደገና ላክ", "privacy_policy": "Privacy Policy",
"code_sent_to_phone": "ኮዱ ወደ ስልክ ቁጥርዎ ተልኳል", "register_with_email": "Register with email",
"code_sent_to_email": "ኮዱ ወደ ኢሜል ተልኳል", "verification_code": "Verification Code",
"resend_code_in": "ኮዱን እንደገና ለመላክ የቀረው ጊዜ", "resend_code": "Resend Code",
"reset_password": " የይለፍ ቃልን ይቀይሩ", "code_sent_to_phone": "Code sent to your number",
"enter_email_reset_code": "ኢሜይልዎን ያስገቡ። የይለፍ ቃል መለወጫ ኮድ እንልክልዎታለን።", "code_sent_to_email": "Code sent to your email",
"please_wait": "እባክዎ ይጠብቁ", "resend_code_in": "Resend code in",
"reset_code_sent": "የመቀየሪያ ኮድ በተሳካ ሁኔታ ተልኳል", "reset_password": "Reset Password",
"reset_code": " የመቀየሪያ ኮድ ", "enter_email_reset_code":
"new_password": "አዲስ የይለፍ ቃል", "Enter your email. We will send you a reset code.",
"logged_in_successfully": "በተሳካ ሁኔታ ገብተዋል", "please_wait": "Please wait",
"view_course": " ኮርሱን ይመልከቱ", "reset_code_sent": "Reset code sent successfully",
"continue_learning": "መማርን ይቀጥሉ", "reset_code": "Reset code",
"start_learning": "ትምህርትን ይጀምሩ", "new_password": "New password",
"completed": "ተጠናቋል", "logged_in_successfully": "Logged in successfully",
"take_practice": "ልምምድ ያድርጉ", "continue_learning": "Continue Learning",
"your_current_level": "የአሁኑ ደረጃዎ", "start_learning": "Start Learning",
"overall_progress": "አጠቃላይ እድገት", "completed": "Completed",
"great_work": "በርቱ! በጣም ጥሩ እየሰሩ ነው", "view_course": "View course",
"view_module": "ሞጁሉን ይመልከቱ", "take_practice": "Take practice",
"progress": "እድገት", "your_current_level": "Your current level",
"keep_going": "ይቀጥሉ - ከግማሽ በላይ ጨርሰዋል ", "overall_progress": "Overall progress",
"lessons_in_module": "በዚህ ሞጁል ውስጥ ያሉ ትምህርቶች ", "great_work": "Keep up the great work! You're doing amazing",
"practice": "ልምምድ", "view_module": "View module",
"start": "ጀምር", "progress": "Progress",
"in_progress": "በሂደት ላይ", "keep_going": "Let's keep going - you're more than half there",
"hello": "ሰላም", "lessons_in_module": "Lessons in this module",
"ready_to_learn": " ዛሬ እንግሊዝኛ ለመማር ተዘጋጅተዋል? ", "practice": "Practice",
"learn": "ይማሩ ", "start": "Start",
"course": "ኮርስ", "in_progress": "In Progress",
"profile": " ፕሮፋይል ", "hello": "Hello",
"speaking_partner": "የንግግር ጓደኛ", "ready_to_learn": "Ready to keep learning English today",
"practice_what_you_learned": "አሁን የተማሩትን እንለማመድ", "learn": "Learn",
"practice_questions": "ጥቂት ጥያቄዎችን እጠይቃለሁ እና መልስ መስጠት ይችላሉ", "course": "Course",
"start_practice": "ልምምድ ጀምር", "profile": "Profile",
"almost_there": "ሊጨርሱ ተቃርበዋል", "speaking_partner": "Speaking partner",
"finish_session": "እድገትዎን ለማየት ክፍለ ጊዜውን ያጠናቅቁ", "practice_what_you_learned": "Let's practice what you just learnt",
"continue_practice": "ልምምዱን ይቀጥሉ", "practice_questions": "I will ask you a few questions and you can respond",
"end_session": "ክፍለ ጊዜውን ያብቁ ", "start_practice": "Start practice",
"tap_start_to_listen": "ለማዳመጥ የጀምር ቁልፉን ይጫኑ", "almost_there": "You're almost there",
"practice_speaking": "ንግግርን ይለማመዱ", "finish_session": "Finish the session to see your progress",
"tap_microphone": "ለመናገር ማይክሮፎኑን ይጫኑ", "continue_practice": "Continue practice",
"reply": "እንደገና አዳምጥ", "end_session": "End session",
"cancel": "ይቅር", "tap_start_to_listen": "Tap the start button to listen",
"you_are_speaking": "እየተናገሩ ነው", "practice_speaking": "Practice speaking",
"practice_completed": "ልምምዱ ተጠናቅቋል", "tap_microphone": "Tap the microphone to speak",
"great_improvement": "በዚህኛው በራስ መተማመንዎ ጨምሯል፤ ትልቅ መሻሻል ነው", "reply": "Reply",
"practice_again": "እንደገና ይለማመዱ", "cancel": "Cancel",
"conversation_review": "የንግግር ግምገማ", "you_are_speaking": "You're speaking",
"result": "ውጤት", "practice_completed": "Practice completed!",
"quick_tip": "ጠቃሚ ምክር", "great_improvement":
"retry": "እንደገና ይሞክሩ", "You sound more confident this time, great improvement",
"completed_a1": "እንኳን ደስ አለዎት! A1 ደረጃን አጠናቅቀዋል", "practice_again": "Practice again",
"analyzing_speaking": "የንግግር ችሎታዎን እየገመገምን ነው", "conversation_review": "Conversation review",
"view_profile": "ፕሮፋይሎን ይመልከቱ ", "result": "Result",
"hi": "ሰላም", "quick_tip": "Quick tip",
"edit_profile": "መገለጫ ያስተካክሉ", "retry": "Retry",
"first_name": "የመጀመሪያ ስም", "completed_a1": "Yay, you've completed A1",
"last_name": "የአባት ስም", "analyzing_speaking": "We're now analyzing your speaking skill",
"gender": "ፆታ", "view_profile": "View profile",
"male": "ወንድ", "hi": "Hi",
"female": "ሴት", "edit_profile": "Edit profile",
"phone_number": "የስልክ ቁጥር", "first_name": "First name",
"country": "ሀገር", "last_name": "Last name",
"region": "ክልል", "gender": "Gender",
"select_region": "ክልል ይምረጡ", "male": "Male",
"enter_your_city": "ከተማዎን ያስገቡ", "female": "Female",
"occupation": "የስራ መስክ", "phone_number": "Phone number",
"select_occupation": "ሙያዎን ይምረጡ", "country": "Country",
"save_changes": "ለውጦችን ያስቀምጡ", "region": "Region",
"my_progress": "የእኔ እድገት", "select_region": "Select region",
"track_your_achievement": "ስኬቶችዎን እና ተከታታይ የትምህርት ጉዞዎን ይከታተሉ", "enter_your_city": "Enter your city",
"account_and_privacy": "መለያ እና ግላዊነት", "occupation": "Occupation",
"manage_settings": "ቅንብሮችን እና የመተግበሪያ ምርጫዎችን ያስተዳድሩ", "select_occupation": "Select occupation",
"support": "ድጋፍ", "save_changes": "Save changes",
"get_help": "በስልክ ወይም በቴሌግራም እገዛ ያግኙ", "my_progress": "My progress",
"logout": "ውጣ", "track_your_achievement": "Track your achievements and learning streak",
"app_settings": "የመተግበሪያ ቅንብሮች", "account_and_privacy": "Account & Privacy",
"legal_and_information": "ሕጋዊ እና መረጃ", "manage_settings": "Manage settings and app preference",
"change_language": "ቋንቋ ቀይር", "support": "Support",
"terms_and_conditions": "ውሎች እና ሁኔታዎች", "get_help": "Get help through phone or Telegram",
"delete_account": "መለያ ሰርዝ", "logout": "Logout",
"language_preference": "የቋንቋ ምርጫ", "app_settings": "App settings",
"choose_your_language": "ለውጦችን አስቀምጥ", "legal_and_information": "Legal & Information",
"switch_language_anytime": "ቋንቋዎችን በማንኛውም ጊዜ መቀየር ይችላሉ", "change_language": "Change language",
"need_help": "እገዛ ይፈልጋሉ?", "terms_and_conditions": "Terms & Conditions",
"call_support": "የስልክ ድጋፍ", "delete_account": "Delete account",
"talk_with_support": "በቀጥታ ከድጋፍ ቡድናችን ጋር ይነጋገሩ", "language_preference": "Language preference",
"telegram_support": "የቴሌግራም ድጋፍ", "choose_your_language": "Choose your language",
"chat_via_telegram": "በቴሌግራም በፍጥነት ይወያዩ", "switch_language_anytime": "You can switch languages anytime",
"call_our_support": "ከ3 ጠዋት እስከ 12 ማታ ድረስ የድጋፍ ቡድናችንን ይደውሉ", "need_help": "Need help?",
"tap_to_call": "ለመደወል ይንኩ", "call_support": "Call support",
"join_telegram": "በቴሌግራም የይማሩ አካዳሚን ይቀላቀሉ", "talk_with_support": "Talk with our support team directly",
"connect_with_support_team": "ለፈጣን እርዳታ እና የማህበረሰብ ዝማኔዎች፣ በቴሌግራም ከድጋፍ ቡድናችን ጋር ወዲያውኑ ይገናኙ።", "telegram_support": "Telegram support",
"open_in_telegram": "በቴሌግራም ይክፈቱ", "chat_via_telegram": "Chat instantly via Telegram",
"search_for": "ፈልጉት", "call_our_support": "Call our support team between 9 AM - 6 PM",
"current_level": "የአሁኑ ደረጃ", "tap_to_call": "Tap to call",
"keep_up_the_great_work": "በጣም ጥሩ እየሰራህ ነው! ቀጥልበት፣ አስደናቂ ነህ።", "join_telegram": "Join Yimaru Academy on Telegram",
"no_practice_available": "ምንም ልምምድ አልተገኘም!", "connect_with_support_team":
"begin_module_practice": "የሞጁሉን ልምምድ ጀምር", "Connect with our support team instantly on Telegram for quick assistance and community updates",
"lets_practice_lesson": "እንለማመድ", "open_in_telegram": "Open in Telegram",
"lets_quickly_review": "በዚህ ሞጁል ውስጥ የተማርከውን በፍጥነት እንከልስ!", "search_for": "Search for",
"lets_practice_module": "አሁን የተማርከውን እንለማመድ!", "current_level": "Current Level",
"ask_you_few_actions": "ጥቂት ጥያቄዎችን እጠይቅሃለሁ፣ አንተም በተፈጥሮ መልስ ልትሰጥ ትችላለህ።", "keep_up_the_great_work": "Keep up the great work! You're doing amazing.",
"begin_level_practice": "የደረጃ ልምምድን ጀምር", "no_practice_available": "No practice available!",
"lets_practice_course": "የኮርሱን ልምምድ እንለማመድ", "begin_module_practice": "Begin Module Practice",
"lets_quick_review": "በዚህ ደረጃ የተማርከውን በፍጥነት እንከልስ!", "lets_practice_lesson": "Lets Practice",
"speaking": "እየተናገረ ነው", "lets_quickly_review":
"you_have_finished_practice": "ልምምድህን አጠናቀቅህ", "Lets quickly review what youve learned in this module!",
"view_results": "ውጤቶቼን እይ", "lets_practice_module": "Let's practice what you just learnt!",
"sample_answer": "ናሙና መልስ", "ask_you_few_actions":
"your_answer": "መልስህ", "Ill ask you a few questions, and you can respond naturally.",
"sound_confident": "በዚህ ጊዜ የበለጠ እምነት ያለህ ይመስላል — በጣም ጥሩ መሻሻል ነው!", "begin_level_practice": "Begin Level Practice",
"you_have_completed": "አያይ! አጠናቀቅህ", "lets_practice_course": "Lets Practice Course",
"yes": "አዎ", "lets_quick_review":
"no": "አይ", "Lets quickly review what youve learned in this level!",
"want_to_quit": "ለመውጣት እርግጠኛ ነህ?", "speaking": "is speaking...",
"required_field": "ይህ መስክ ያስፈልጋል", "you_have_finished_practice": "You have finished your practice",
"enter_full_name": "ሙሉ ስምህን አስገባ", "view_results": "View My Results",
"invalid_email": "የማይሰራ የኢሜይል ቅርጸት", "sample_answer": "Sample Answer",
"phone_must_start_with": "የስልክ ቁጥር በ251 መጀመር አለበት", "your_answer": "Your Answer",
"phone_must_be": "የስልክ ቁጥር 12 አሃዞች መሆን አለበት", "sound_confident":
"what_should_we_call_you": "ምን ብለን እንጠራህ?", "You sound more confident this time - great improvement!",
"name_for_personalization": "በመማር ጉዞህ ውስጥ ለግል ለማድረግ ስምህን እንጠቀማለን።", "you_have_completed": "Yay, youve completed",
"choose_your_gender": "ጾታህን ምረጥ", "yes": "Yes",
"gender_for_personalization": "በጾታህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።", "no": "No",
"age_range": "በየትኛው የእድሜ ክልል ውስጥ ነህ?", "want_to_quit": "Are you sure you want to quit?",
"age_for_personalization": "በእድሜህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።", "required_field": "The field is required",
"educational_background": "አሁን ያለህ የትምህርት ደረጃ ምንድን ነው?", "enter_full_name": "Enter your full name",
"education_for_personalization": "ይህ ትምህርቶችን ከልምድህ ጋር እንዲስማሙ ለማድረግ ይረዳናል።", "invalid_email": "Invalid email format",
"your_occupation": "ስራህ ምንድን ነው?", "phone_must_start_with": "Phone number must start with 251",
"occupation_for_personalization": "በስራህ መሰረት የመማር ተሞክሮህን እናበጅለታለን።", "phone_must_be": "Phone number must be 12 digits",
"location": "ከየት ነህ?", "what_should_we_call_you": "What should we call you?",
"select_country_region": "አገርህን እና ክልልህን ከተቆልቋይ ዝርዝሩ ምረጥ", "name_for_personalization":
"select_country": "አገር ምረጥ", "Well use your name to personalize your learning journey.",
"learning_goal": "የመማር ዓላማህን ምረጥ", "choose_your_gender": "Choose your gender?",
"language_goal": "እንግሊዝኛህን ለማሻሻል ዋና ዓላማህ ምንድን ነው?", "gender_for_personalization":
"your_goal": "ዓላማህ የመማር ጉዞህን እንዲስማማ ለማድረግ ይረዳናል።", "Well personalize your learning experience based on your gender.",
"write_your_goal": "ዓላማህን ጻፍ…", "age_range": "Which age range are you in?",
"challenge_you_face": "What challenge do you face most with English?", "age_for_personalization":
"evey_one_has_strugle": "ሁሉም ሰው ችግሮች አሉት፣ የአንተን እንጀምር እንፍታ", "Well personalize your learning experience based on your age.",
"write_your_challenge": "ችግርህን ጻፍ…", "educational_background": "Whats your current educational level?",
"topic_interest": "በጣም የሚስቡህ ርዕሶች የትኞቹ ናቸው?", "education_for_personalization":
"favourite_topic": "የምትወዳቸው ርዕሶች አስደሳች እና ከሕይወትህ ጋር የተዛመዱ ትምህርቶችን ለመፍጠር ይረዱናል።", "This helps us tailor your lessons to your experience.",
"your_interest": "ፍላጎትህን ጻፍ…", "your_occupation": "Whats your occupation?",
"want_quick_assessment": "የእንግሊዝኛ ደረጃህን ለማወቅ ፈጣን ግምገማ ትፈልጋለህ?", "occupation_for_personalization":
"answer_quick_questions": "የእንግሊዝኛ ችሎታህን ለመረዳት ጥቂት ፈጣን ጥያቄዎችን መልስ።", "Well personalize your learning experience based on your occupation.",
"skip": "ዝለል", "location": "Where are you from?",
"finish_level": "ደረጃውን አጠናቅቅ", "select_country_region": "Select your country and region from the dropdown",
"likely_speaker": "አንተ ምናልባት ተናጋሪ ነህ", "select_country": "Select country",
"great_job": "በጣም ጥሩ ስራ! ለመሻሻል ቀጣዩ ደረጃህ ይኸው ነው።", "learning_goal": "Choose your learning goal.",
"lets_start_practice": "ልምምድህን እንጀምር", "language_goal": "Whats your main goal for improving your English?",
"welcome_abroad": "እንኳን ደህና መጣህ", "your_goal": "Your goal helps us tailor your learning journey.",
"ready_to_explore": "የግል ትምህርቶችህን ለማሰስ ዝግጁ ነህ።", "write_your_goal": "Write your goal…",
"finish": "አጠናቅቅ", "challenge_you_face": "What challenge do you face most with English?",
"finish_all_practice_lesson": "ይህን ልምምድ ለመውሰድ የቀድሞውን የትምህርት ልምምድ ያጠናቅቁ", "evey_one_has_strugle": "Everyone has struggles, lets start fixing yours",
"finish_all_practice_module": "የሞጁሉን ልምምድ ለመውሰድ የትምህርት ልምምዶችን ያጠናቅቁ", "write_your_challenge": "Write your challenge…",
"finish_all_practice_course": "የኮርሱን ልምምድ ለመውሰድ የሞጁል ልምምዶችን ያጠናቅቁ", "topic_interest": "Which topics interest you most?",
"finish_all_practice_previouse_module": "ይህን ልምምድ ለመውሰድ የቀድሞውን የሞጁል ልምምድ ያጠናቅቁ", "favourite_topic":
"finish_all_practice_previouse_course": "ይህን ለመውሰድ የቀድሞውን የኮርስ ልምምድ ያጠናቅቁ", "Your favorite topics help us create fun, relatable lessons.",
"track_journey": "የትምህርት ጉዞዎን ይከታተሉ እና በጊዜ ሂደት ያሳዩትን እድገት ይመልከቱ።", "your_interest": "Write your interest…",
"learn_english": "እንግሊዝኛ ይማሩ", "want_quick_assessment":
"keep_momentum": "በጣም ጥሩ ስራ! በዚሁ ብርታት ይቀጥሉ።", "Want a quick assessment to know your English level?",
"completed_practices": "የተጠናቀቁ ልምምዶች", "answer_quick_questions":
"total_practices": "ጠቅላላ ልምምዶች", "Answer a few quick questions to help us understand your English proficiency.",
"progress_percentage": "የእድገት መቶኛ" "skip": "Skip",
}; "finish_level": "Finish Level",
static const Map<String, Map<String,dynamic>> mapLocales = {"en": _en, "am": _am}; "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 // ignore_for_file: constant_identifier_names
abstract class LocaleKeys { abstract class LocaleKeys {
static const loading = 'loading'; static const loading = 'loading';
static const welcome_back = 'welcome_back'; static const welcome_back = 'welcome_back';
static const checking_user_info = 'checking_user_info'; static const checking_user_info = 'checking_user_info';
@ -13,14 +13,12 @@ abstract class LocaleKeys {
static const cont = 'cont'; static const cont = 'cont';
static const register = 'register'; static const register = 'register';
static const login_with_google = 'login_with_google'; static const login_with_google = 'login_with_google';
static const login_with_apple = 'login_with_apple';
static const or = 'or'; static const or = 'or';
static const login_with_phone = 'login_with_phone'; static const login_with_phone = 'login_with_phone';
static const create_account = 'create_account'; static const create_account = 'create_account';
static const already_have_account = 'already_have_account'; static const already_have_account = 'already_have_account';
static const login = 'login'; static const login = 'login';
static const register_with_google = 'register_with_google'; static const register_with_google = 'register_with_google';
static const register_with_apple = 'register_with_apple';
static const register_with_phone = 'register_with_phone'; static const register_with_phone = 'register_with_phone';
static const enter_phone_number = 'enter_phone_number'; static const enter_phone_number = 'enter_phone_number';
static const login_with_email = 'login_with_email'; static const login_with_email = 'login_with_email';
@ -45,10 +43,10 @@ abstract class LocaleKeys {
static const reset_code = 'reset_code'; static const reset_code = 'reset_code';
static const new_password = 'new_password'; static const new_password = 'new_password';
static const logged_in_successfully = 'logged_in_successfully'; static const logged_in_successfully = 'logged_in_successfully';
static const view_course = 'view_course';
static const continue_learning = 'continue_learning'; static const continue_learning = 'continue_learning';
static const start_learning = 'start_learning'; static const start_learning = 'start_learning';
static const completed = 'completed'; static const completed = 'completed';
static const view_course = 'view_course';
static const take_practice = 'take_practice'; static const take_practice = 'take_practice';
static const your_current_level = 'your_current_level'; static const your_current_level = 'your_current_level';
static const overall_progress = 'overall_progress'; static const overall_progress = 'overall_progress';
@ -163,7 +161,8 @@ abstract class LocaleKeys {
static const educational_background = 'educational_background'; static const educational_background = 'educational_background';
static const education_for_personalization = 'education_for_personalization'; static const education_for_personalization = 'education_for_personalization';
static const your_occupation = 'your_occupation'; 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 location = 'location';
static const select_country_region = 'select_country_region'; static const select_country_region = 'select_country_region';
static const select_country = 'select_country'; 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_lesson = 'finish_all_practice_lesson';
static const finish_all_practice_module = 'finish_all_practice_module'; 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_course = 'finish_all_practice_course';
static const finish_all_practice_previouse_module = 'finish_all_practice_previouse_module'; static const finish_all_practice_previouse_module =
static const finish_all_practice_previouse_course = 'finish_all_practice_previouse_course'; '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 track_journey = 'track_journey';
static const learn_english = 'learn_english'; static const learn_english = 'learn_english';
static const keep_momentum = 'keep_momentum'; static const keep_momentum = 'keep_momentum';
static const completed_practices = 'completed_practices'; static const completed_practices = 'completed_practices';
static const total_practices = 'total_practices'; static const total_practices = 'total_practices';
static const progress_percentage = 'progress_percentage'; static const progress_percentage = 'progress_percentage';
static const notifications = 'notifications';
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/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/app_colors.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
@ -26,94 +30,38 @@ class FailureView extends StackedView<FailureViewModel> {
_buildScaffoldWrapper(); _buildScaffoldWrapper();
Widget _buildScaffoldWrapper() => Scaffold( Widget _buildScaffoldWrapper() => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcPrimaryColor,
body: _buildScaffold(), body: _buildStartupScreens(),
); );
Widget _buildScaffold() => Stack( Widget _buildStartupScreens() => FlutterCarousel(
children: _buildScaffoldChildren(), options: FlutterCarouselOptions(
autoPlay: true,
viewportFraction: 1,
showIndicator: false,
height: double.maxFinite,
),
items: _buildScreens(),
); );
List<Widget> _buildScaffoldChildren() => [ List<Widget> _buildScreens() => [
_buildBackground(), _buildFirstFailure(),
_buildColumn(), _buildSecondFailure(),
_buildThirdFailure(),
]; ];
Widget _buildBackground() => Image.asset( Widget _buildFirstFailure() => FirstFailureScreen(
'assets/images/loading.png', label: label,
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(
onTap: onTap, onTap: onTap,
child: _buildRetryButton(),
); );
Widget _buildRetryButton() => Text( Widget _buildSecondFailure() => SecondFailureScreen(
'Retry', label: label,
style: style16W600.copyWith( onTap: onTap,
fontStyle: FontStyle.italic, decoration: TextDecoration.underline), );
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/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/views/course/course_view.dart';
import 'package:yimaru_app/ui/views/course_catalog/course_catalog_view.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart'; import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
@ -17,6 +19,8 @@ class HomeView extends StackedView<HomeViewModel> {
@override @override
void onViewModelReady(HomeViewModel viewModel) async { void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.inAppUpdate(); await viewModel.inAppUpdate();
await viewModel.getUnreadNotifications();
super.onViewModelReady(viewModel); super.onViewModelReady(viewModel);
} }
@ -80,7 +84,7 @@ class HomeView extends StackedView<HomeViewModel> {
case 0: case 0:
return const LearnProgramView(); return const LearnProgramView();
case 1: case 1:
return const ComingSoon(); return const CourseView();
default: default:
return const ProfileView(); 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 'package:yimaru_app/ui/common/enmus.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/in_app_update_service.dart'; import '../../../services/in_app_update_service.dart';
class HomeViewModel extends ReactiveViewModel { class HomeViewModel extends ReactiveViewModel {
// Dependency injection // Dependency injection
final _statusChecker = locator<StatusCheckerService>(); final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _bottomSheetService = locator<BottomSheetService>(); final _bottomSheetService = locator<BottomSheetService>();
@ -22,9 +22,11 @@ class HomeViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_authenticationService]; [_authenticationService,_inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;
@ -55,8 +57,6 @@ class HomeViewModel extends ReactiveViewModel {
rebuildUi(); rebuildUi();
} }
// Remote api calls // Remote api calls
// In-app update // In-app update
@ -65,4 +65,12 @@ class HomeViewModel extends ReactiveViewModel {
await _inAppUpdateService.checkForUpdate(); 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:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart'; import 'package:yimaru_app/ui/views/landing/screens/first_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/fourth_landing_screen.dart';
import 'package:yimaru_app/ui/views/landing/screens/second_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'; import 'package:yimaru_app/ui/views/landing/screens/third_landing_screen.dart';
@ -44,17 +43,15 @@ class LandingView extends StackedView<LandingViewModel> {
); );
List<Widget> _buildScreens() => [ List<Widget> _buildScreens() => [
_buildFirstWelcome(), _buildFirstLanding(),
_buildSecondWelcome(), _buildSecondLanding(),
_buildThirdWelcome(), _buildThirdLanding(),
_buildFourthWelcome()
]; ];
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( Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg', 'assets/icons/logo_white.svg',
height: 25, height: 25,
); );
@ -100,7 +100,7 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
); );
Widget _buildImage() => Image.asset( Widget _buildImage() => Image.asset(
'assets/images/landing_1.jpg', 'assets/images/landing_1.png',
fit: BoxFit.cover, fit: BoxFit.cover,
); );
@ -126,10 +126,10 @@ class FirstLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
onTap: viewModel.next, text: 'Get Started',
backgroundColor: kcWhite, backgroundColor: kcWhite,
foregroundColor: kcPrimaryColor, 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( Widget _buildIcon() => SvgPicture.asset(
'assets/icons/logo.svg', 'assets/icons/logo_purple.svg',
color: kcPrimaryColor, color: kcPrimaryColor,
height: 25, height: 25,
); );
@ -101,7 +101,7 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
); );
Widget _buildImage() => Image.asset( Widget _buildImage() => Image.asset(
'assets/images/landing_2.jpg', 'assets/images/landing_2.png',
fit: BoxFit.cover, fit: BoxFit.cover,
); );
@ -127,10 +127,10 @@ class SecondLandingScreen extends ViewModelWidget<LandingViewModel> {
Widget _buildContinueButton(LandingViewModel viewModel) => Widget _buildContinueButton(LandingViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Next',
borderRadius: 25, borderRadius: 25,
onTap: viewModel.next, text: 'Get Started',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.setFirstTimeInstall(),
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,21 +31,8 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Future<void> _onPractice( Future<void> _onPractice(
{required LearnLesson lesson, {required LearnLesson lesson,
required LearnLessonDetailViewModel viewModel}) async { required LearnLessonDetailViewModel viewModel}) async {
/* await viewModel.pause(); await viewModel.pause();
await viewModel.navigateToLearnPractice(lesson.id ?? 0); 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 @override

View File

@ -16,27 +16,26 @@ import '../../widgets/small_app_bar.dart';
import 'learn_module_viewmodel.dart'; import 'learn_module_viewmodel.dart';
class LearnModuleView extends StackedView<LearnModuleViewModel> { class LearnModuleView extends StackedView<LearnModuleViewModel> {
final bool first;
final LearnCourse course; 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( Future<void> _onPractice(
{required BuildContext context, {required BuildContext context,
required LearnModule module, required LearnModule module,
required LearnModuleViewModel viewModel}) async { required LearnModuleViewModel viewModel}) async {
if (module.access?.completedCount == module.access?.totalCount) { if (module.access?.isCompleted ?? false ) {
await viewModel.navigateToLearnPractice( await viewModel.navigateToLearnPractice(
id: module.id ?? 0, module: module.name ?? ''); id: module.id ?? 0, module: module.name ?? '');
} else { } else {
if (module.access?.isAccessible ?? false) { if (module.access?.isAccessible ?? false) {
print('Accessible');
await _showSheet( await _showSheet(
context: context, context: context,
viewModel: viewModel, viewModel: viewModel,
practice: PracticeReason.module); practice: PracticeReason.module);
} else { } else {
print('Inaccessible');
await _showSheet( await _showSheet(
context: context, context: context,
viewModel: viewModel, viewModel: viewModel,
@ -201,8 +200,8 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
viewModel: viewModel, viewModel: viewModel,
module: viewModel.modules[index], module: viewModel.modules[index],
), ),
onModuleTap: () async => onModuleTap: () async => await viewModel.navigateToLearnLesson(
await viewModel.navigateToLearnLesson(viewModel.modules[index]), first: first && index == 0, module: viewModel.modules[index]),
), ),
); );

View File

@ -35,8 +35,10 @@ class LearnModuleViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLesson(LearnModule module) async => Future<void> navigateToLearnLesson(
await _navigationService.navigateToLearnLessonView(module: module); {required bool first, required LearnModule module}) async =>
await _navigationService.navigateToLearnLessonView(
first: first, module: module);
Future<void> navigateToLearnPractice( Future<void> navigateToLearnPractice(
{required int id, required String module}) async => {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 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/learn_question.dart'; import '../../../models/learn_question.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/audio_player_service.dart'; import '../../../services/audio_player_service.dart';
@ -264,6 +265,9 @@ class LearnPracticeViewModel extends ReactiveViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnSubscription() async =>
await _navigationService.navigateToLearnSubscriptionView();
// Remote api call // Remote api call
// Refresh url // Refresh url

View File

@ -27,6 +27,14 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
required this.subtitle, required this.subtitle,
required this.practice}); 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 { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording(); await viewModel.stopRecording();
viewModel.pop(); viewModel.pop();
@ -207,7 +215,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
text: label, text: label,
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _practice(viewModel),
); );
} }

View File

@ -61,6 +61,8 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
Widget _buildAppBar(LearnProgramViewModel viewModel) => ProfileAppBar( Widget _buildAppBar(LearnProgramViewModel viewModel) => ProfileAppBar(
name: viewModel.user?.firstName, name: viewModel.user?.firstName,
profileImage: viewModel.user?.profilePicture, profileImage: viewModel.user?.profilePicture,
unreadCount: viewModel.unreadCount.toString(),
onTap: () async => await viewModel.navigateToNotification(),
); );
Widget _buildProgramsColumnWrapper(LearnProgramViewModel viewModel) => Widget _buildProgramsColumnWrapper(LearnProgramViewModel viewModel) =>
@ -87,8 +89,8 @@ class LearnProgramView extends StackedView<LearnProgramViewModel> {
separatorBuilder: (context, index) => verticalSpaceSmall, separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
program: viewModel.learnPrograms[index], program: viewModel.learnPrograms[index],
onTap: () async => await viewModel onTap: () async => await viewModel.navigateToLearnCourse(
.navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0), 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 '../../../app/app.locator.dart';
import '../../../models/user.dart'; import '../../../models/user.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/in_app_notification_service.dart';
import '../../../services/learn_service.dart'; import '../../../services/learn_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
@ -21,23 +22,34 @@ class LearnProgramViewModel extends ReactiveViewModel {
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
final _inAppNotificationService = locator<InAppNotificationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_learnService, _authenticationService]; [_learnService, _authenticationService,_inAppNotificationService];
// Current user // Current user
User? get _user => _authenticationService.user; User? get _user => _authenticationService.user;
User? get user => _user; User? get user => _user;
// Notification count
int get _unreadCount => _inAppNotificationService.unreadCount;
int get unreadCount => _unreadCount;
// Learn programs // Learn programs
List<LearnProgram> get _learnPrograms => _learnService.programs; List<LearnProgram> get _learnPrograms => _learnService.programs;
List<LearnProgram> get learnPrograms => _learnPrograms; List<LearnProgram> get learnPrograms => _learnPrograms;
// Navigation // Navigation
Future<void> navigateToLearnCourse(int id) async => Future<void> navigateToNotification() async =>
_navigationService.navigateToLearnCourseView(id: id); await _navigationService.navigateToNotificationView();
Future<void> navigateToLearnCourse(
{required int id, required bool first}) async =>
_navigationService.navigateToLearnCourseView(id: id, first: first);
// Remote api call // 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 'package:yimaru_app/models/user.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/apple_auth_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../../services/google_auth_service.dart'; import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart'; import '../../../services/localization_service.dart';
@ -26,23 +25,19 @@ class LoginViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _appleAuthService = locator<AppleAuthService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_googleAuthService, _appleAuthService, _localizationService]; [_googleAuthService, _localizationService];
// Google user // Google user
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser; GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
GoogleSignInAccount? get googleUser => _googleUser; GoogleSignInAccount? get googleUser => _googleUser;
bool get isAppleSignInAvailable => _appleAuthService.isSupported;
// Languages // Languages
Map<String, dynamic> get _selectedLanguage => Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage; _localizationService.selectedLanguage;
@ -240,50 +235,6 @@ class LoginViewModel extends ReactiveViewModel
Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(), Future<void> signInWithGoogle() async => await runBusyFuture(_googleAuth(),
busyObject: StateObjects.loginWithGoogle); busyObject: StateObjects.loginWithGoogle);
// Sign-in with Apple
Future<void> _appleAuth() async {
if (await _statusChecker.checkConnection()) {
await _appleAuthService.appleAuth();
final credential = _appleAuthService.appleCredential;
final identityToken = credential?.identityToken;
if (identityToken == null || identityToken.isEmpty) {
showErrorToast('Apple login failed. Please try again.');
return;
}
Map<String, dynamic> data = {
'id_token': identityToken,
'email': credential?.email,
'first_name': credential?.givenName,
'last_name': credential?.familyName,
};
data.removeWhere((_, value) => value == null || value == '');
Map<String, dynamic> response = await _apiService.appleAuth(data);
if (response['status'] == ResponseStatus.success) {
User user = response['data'] as User;
Map<String, dynamic> data = {
'userId': user.userId,
'accessToken': user.accessToken,
'refreshToken': user.refreshToken
};
await _authenticationService.saveUserCredential(data);
clearUserData();
await replaceWithStartUp();
showSuccessToast(response['message']);
} else {
showErrorToast(response['message']);
}
}
}
Future<void> signInWithApple() async => await runBusyFuture(_appleAuth(),
busyObject: StateObjects.loginWithApple);
// Login with phone // Login with phone
Future<void> loginWithPhoneNumber() async => Future<void> loginWithPhoneNumber() async =>
await runBusyFuture(_loginWithPhoneNumber(), await runBusyFuture(_loginWithPhoneNumber(),

View File

@ -66,8 +66,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
Stack(children: [ Stack(children: [
_buildScaffold(context: context, viewModel: viewModel), _buildScaffold(context: context, viewModel: viewModel),
_buildLoginWithEmailState(viewModel), _buildLoginWithEmailState(viewModel),
_buildLoginWithGoogleState(viewModel), _buildLoginWithGoogleState(viewModel)
_buildLoginWithAppleState(viewModel)
]); ]);
Widget _buildScaffold( Widget _buildScaffold(
@ -233,8 +232,6 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
List<Widget> _buildLowerColumnChildren(LoginViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(LoginViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
_buildLoginWithGoogleButton(viewModel), _buildLoginWithGoogleButton(viewModel),
if (viewModel.isAppleSignInAvailable)
_buildLoginWithAppleButton(viewModel),
_buildOptionTextDivider(), _buildOptionTextDivider(),
_buildLoginWithPhoneButton(viewModel), _buildLoginWithPhoneButton(viewModel),
verticalSpaceMedium verticalSpaceMedium
@ -268,18 +265,6 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
onTap: () async => await viewModel.signInWithGoogle(), onTap: () async => await viewModel.signInWithGoogle(),
); );
Widget _buildLoginWithAppleButton(LoginViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
text: LocaleKeys.login_with_apple.tr(),
leadingIcon: Icons.apple,
onTap: () async => await viewModel.signInWithApple(),
);
Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildLoginWithPhoneButton(LoginViewModel viewModel) => Widget _buildLoginWithPhoneButton(LoginViewModel viewModel) =>
@ -302,9 +287,4 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
viewModel.busy(StateObjects.loginWithGoogle) viewModel.busy(StateObjects.loginWithGoogle)
? const PageLoadingIndicator() ? const PageLoadingIndicator()
: Container(); : Container();
Widget _buildLoginWithAppleState(LoginViewModel viewModel) =>
viewModel.busy(StateObjects.loginWithApple)
? const PageLoadingIndicator()
: Container();
} }

View File

@ -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; _selectedCountry = value;
if (value?.code?.toLowerCase().trim() == 'et') { if (value?.code?.toLowerCase().trim() == 'et') {
_dropdownRegion = true; _dropdownRegion = true;
_selectedRegion = _regions _selectedRegion = _regions.firstWhere(
.firstWhere((e) => e.code?.toLowerCase().trim().contains('addis_ababa') ?? false); (e) => e.code?.toLowerCase().trim().contains('addis_ababa') ?? false);
} else { } else {
_dropdownRegion = false; _dropdownRegion = false;
} }

View File

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

View File

@ -45,18 +45,22 @@ class PaymentViewModel extends ReactiveViewModel {
// Remote api call // Remote api call
// Learn subscription // Learn subscription
Future<void> createLearnSubscriptionRequest({required String phone,required LearnSubscription subscription}) async => Future<void> createLearnSubscriptionRequest(
await runBusyFuture(_createLearnSubscriptionRequest(phone: phone,subscription: subscription), {required String phone,
required LearnSubscription subscription}) async =>
await runBusyFuture(
_createLearnSubscriptionRequest(
phone: phone, subscription: subscription),
busyObject: StateObjects.learnSubscription); 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()) { if (await _statusChecker.checkConnection()) {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'provider': 'CHAPA', 'provider': 'CHAPA',
'phone': '251$phone', 'phone': '251$phone',
'email': 'test@gmail.com', 'email': 'test@gmail.com',
'plan_id': subscription.id, 'plan_id': subscription.id,
}; };
Map<String, dynamic> response = 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/enmus.dart';
import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart';
import 'package:yimaru_app/ui/common/ui_helpers.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_card.dart';
import 'package:yimaru_app/ui/widgets/profile_image.dart'; import 'package:yimaru_app/ui/widgets/profile_image.dart';
import 'package:yimaru_app/ui/widgets/view_profile_button.dart'; import 'package:yimaru_app/ui/widgets/view_profile_button.dart';
@ -89,8 +90,9 @@ class ProfileView extends StackedView<ProfileViewModel> {
required ProfileViewModel viewModel}) => required ProfileViewModel viewModel}) =>
Column( Column(
children: [ children: [
verticalSpaceSmall,
verticalSpaceMedium, verticalSpaceMedium,
_buildNotificationIconWrapper(), _buildNotificationIconWrapper(viewModel),
_buildProfileSection(context: context, viewModel: viewModel), _buildProfileSection(context: context, viewModel: viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildViewProfileButton(viewModel), _buildViewProfileButton(viewModel),
@ -102,12 +104,10 @@ class ProfileView extends StackedView<ProfileViewModel> {
], ],
); );
Widget _buildNotificationIconWrapper() => Widget _buildNotificationIconWrapper(ProfileViewModel viewModel) =>
Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon()); NotificationIcon(
count: viewModel.unreadCount.toString(),
Widget _buildNotificationIcon() => const Icon( onTap: () async => await viewModel.navigateToNotification(),
Icons.notifications_none,
color: kcDarkGrey,
); );
Widget _buildProfileSection( Widget _buildProfileSection(
@ -166,7 +166,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [ List<Widget> _buildSettingsChildren(ProfileViewModel viewModel) => [
// _buildDownloadsCard(viewModel), // _buildDownloadsCard(viewModel),
_buildProgressCard(viewModel), _buildProgressCard(viewModel),
_buildAccountCard(viewModel), _buildAccountCard(viewModel),
_buildSupportCard(viewModel) _buildSupportCard(viewModel)
]; ];

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@ class ProgressViewModel extends ReactiveViewModel {
@override @override
List<ListenableServiceMixin> get listenableServices => [_learnService]; List<ListenableServiceMixin> get listenableServices => [_learnService];
// Total practice count // Total practice count
int get _totalCount => _learnService.totalCount; int get _totalCount => _learnService.totalCount;
@ -33,7 +32,6 @@ class ProgressViewModel extends ReactiveViewModel {
int get totalProgress => _totalProgress; int get totalProgress => _totalProgress;
// Courses // Courses
final List<Map<String, dynamic>> _courses = [ final List<Map<String, dynamic>> _courses = [
{ {
@ -53,8 +51,9 @@ class ProgressViewModel extends ReactiveViewModel {
// Learning progress // Learning progress
Future<void> getProgressSummary() async => runBusyFuture(_getProgressSummary(), Future<void> getProgressSummary() async =>
busyObject: StateObjects.progressSummary); runBusyFuture(_getProgressSummary(),
busyObject: StateObjects.progressSummary);
Future<void> _getProgressSummary() async { Future<void> _getProgressSummary() async {
if (await _statusCheckerService.checkConnection()) { 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 '../../../app/app.locator.dart';
import '../../../models/user.dart'; import '../../../models/user.dart';
import '../../../services/apple_auth_service.dart';
import '../../../services/google_auth_service.dart'; import '../../../services/google_auth_service.dart';
import '../../../services/localization_service.dart'; import '../../../services/localization_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
@ -26,23 +25,19 @@ class RegisterViewModel extends ReactiveViewModel
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _appleAuthService = locator<AppleAuthService>();
final _localizationService = locator<LocalizationService>(); final _localizationService = locator<LocalizationService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@override @override
List<ListenableServiceMixin> get listenableServices => List<ListenableServiceMixin> get listenableServices =>
[_googleAuthService, _appleAuthService, _localizationService]; [_googleAuthService, _localizationService];
// Google user // Google user
GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser; GoogleSignInAccount? get _googleUser => _googleAuthService.googleUser;
GoogleSignInAccount? get googleUser => _googleUser; GoogleSignInAccount? get googleUser => _googleUser;
bool get isAppleSignInAvailable => _appleAuthService.isSupported;
// Languages // Languages
Map<String, dynamic> get _selectedLanguage => Map<String, dynamic> get _selectedLanguage =>
_localizationService.selectedLanguage; _localizationService.selectedLanguage;
@ -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 => Future<void> verifyOtp() async =>
await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp); await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp);

View File

@ -65,9 +65,7 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
Stack( Stack(
children: [ children: [
_buildScaffold(context: context, viewModel: viewModel), _buildScaffold(context: context, viewModel: viewModel),
_buildRegisterWithEmailState(viewModel), _buildRegisterWithEmailState(viewModel)
_buildRegisterWithGoogleState(viewModel),
_buildRegisterWithAppleState(viewModel)
], ],
); );
@ -193,8 +191,6 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
List<Widget> _buildLowerColumnChildren(RegisterViewModel viewModel) => [ List<Widget> _buildLowerColumnChildren(RegisterViewModel viewModel) => [
_buildContinueButton(viewModel), _buildContinueButton(viewModel),
_buildRegisterWithGoogleButton(viewModel), _buildRegisterWithGoogleButton(viewModel),
if (viewModel.isAppleSignInAvailable)
_buildRegisterWithAppleButton(viewModel),
_buildOptionTextDivider(), _buildOptionTextDivider(),
_buildRegisterWithEmailButton(viewModel), _buildRegisterWithEmailButton(viewModel),
verticalSpaceMedium verticalSpaceMedium
@ -229,18 +225,6 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
onTap: () async => await viewModel.registerWithGoogle(), 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 _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildRegisterWithEmailButton(RegisterViewModel viewModel) => Widget _buildRegisterWithEmailButton(RegisterViewModel viewModel) =>
@ -259,14 +243,4 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
viewModel.busy(StateObjects.register) viewModel.busy(StateObjects.register)
? const PageLoadingIndicator() ? const PageLoadingIndicator()
: Container(); : 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:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.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 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
@ -13,6 +17,7 @@ import 'startup_viewmodel.dart';
class StartupView extends StackedView<StartupViewModel> { class StartupView extends StackedView<StartupViewModel> {
final String? label; final String? label;
const StartupView({Key? key, this.label}) : super(key: key); const StartupView({Key? key, this.label}) : super(key: key);
@override @override
@ -24,83 +29,36 @@ class StartupView extends StackedView<StartupViewModel> {
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(StartupViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(StartupViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcPrimaryColor,
body: _buildScaffoldState(viewModel), body: _buildStartupScreens(viewModel),
); );
Widget _buildScaffoldState(StartupViewModel viewModel) => Widget _buildStartupScreens(StartupViewModel viewModel) => FlutterCarousel(
viewModel.busy(StateObjects.startupView) options: FlutterCarouselOptions(
? _buildStartUpView() autoPlay: true,
: _buildScaffold(); viewportFraction: 1,
showIndicator: false,
Widget _buildStartUpView() => height: double.maxFinite,
StartupView(label: LocaleKeys.checking_user_info.tr()); ),
items: _buildScreens(),
Widget _buildScaffold() => Stack(
children: _buildScaffoldChildren(),
); );
List<Widget> _buildScaffoldChildren() => [ List<Widget> _buildScreens() => [
_buildBackground(), _buildFirstStartup(),
_buildColumn(), _buildSecondStartup(),
_buildThirdWelcome(),
]; ];
Widget _buildBackground() => Image.asset( Widget _buildFirstStartup() => FirstStartupScreen(
'assets/images/loading.png', label: label,
fit: BoxFit.fill,
width: double.maxFinite,
height: double.maxFinite,
); );
Widget _buildColumn() => Column( Widget _buildSecondStartup() => SecondStartupScreen(
mainAxisSize: MainAxisSize.max, label: label,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildUpperColumnChildren(),
); );
List<Widget> _buildUpperColumnChildren() => Widget _buildThirdWelcome() => ThirdStartupScreen(
[_buildIconWrapper(), _buildSafeWrapper()]; label: label,
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,
); );
@override @override

View File

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

View File

@ -17,6 +17,7 @@ import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> { class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
final int index; final int index;
final bool last; final bool last;
final bool first;
final LearnLesson lesson; final LearnLesson lesson;
final GestureTapCallback? onLessonTap; final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap; final GestureTapCallback? onPracticeTap;
@ -26,6 +27,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
this.onLessonTap, this.onLessonTap,
this.onPracticeTap, this.onPracticeTap,
required this.last, required this.last,
required this.first,
required this.index, required this.index,
required this.lesson}); required this.lesson});
@ -35,7 +37,13 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildContainerWrapper(LearnLessonViewModel viewModel) => Widget _buildContainerWrapper(LearnLessonViewModel viewModel) =>
GestureDetector( 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), child: _buildContainer(viewModel),
); );
@ -56,7 +64,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildTileStack(LearnLessonViewModel viewModel) => Stack( Widget _buildTileStack(LearnLessonViewModel viewModel) => Stack(
children: [ children: [
_buildExpansionTile(viewModel), _buildExpansionTile(viewModel),
_buildContainerShaderState() _buildContainerShaderState(viewModel)
], ],
); );
@ -70,7 +78,6 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
shape: Border.all(color: kcTransparent), shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft, expandedAlignment: Alignment.centerLeft,
leading: _buildLeadingWrapper(viewModel), leading: _buildLeadingWrapper(viewModel),
enabled: (lesson.access?.isAccessible ?? false),
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15), tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
@ -85,6 +92,7 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
collapsedBackgroundColor: (lesson.access?.isCompleted ?? false) collapsedBackgroundColor: (lesson.access?.isCompleted ?? false)
? kcGreen.withOpacity(0.1) ? kcGreen.withOpacity(0.1)
: kcPrimaryColor.withOpacity(0.1), : kcPrimaryColor.withOpacity(0.1),
enabled: first ? true : (lesson.access?.isAccessible ?? false),
children: _buildExpansionTileChildren(viewModel), children: _buildExpansionTileChildren(viewModel),
); );
@ -203,9 +211,14 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
Widget _buildContainerShaderState() => !(lesson.access?.isAccessible ?? false) Widget _buildContainerShaderState(LearnLessonViewModel viewModel) =>
? _buildContainerShader() viewModel.user?.subscriptionStatus?.toLowerCase() == 'active'
: Container(); ? !(lesson.access?.isAccessible ?? false)
? _buildContainerShader()
: Container()
: !first
? _buildContainerShader()
: Container();
Widget _buildContainerShader() => const CustomContainerShader(); 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/app_colors.dart';
import '../common/translations/locale_keys.g.dart'; import '../common/translations/locale_keys.g.dart';
import 'notification_icon.dart';
class ProfileAppBar extends StatelessWidget { class ProfileAppBar extends StatelessWidget {
final String? name; final String? name;
final String unreadCount;
final String? profileImage; final String? profileImage;
final GestureTapCallback? onTap;
const ProfileAppBar( const ProfileAppBar(
{super.key, required this.name, required this.profileImage}); {super.key, this.onTap, required this.name,required this.unreadCount, required this.profileImage});
@override @override
Widget build(BuildContext context) => _buildStack(); Widget build(BuildContext context) => _buildStack();
@ -91,11 +94,8 @@ class ProfileAppBar extends StatelessWidget {
style: style14DG400, style: style14DG400,
); );
Widget _buildNotificationIconWrapper() =>
Align(alignment: Alignment.bottomRight, child: _buildNotificationIcon());
Widget _buildNotificationIcon() => const Icon( Widget _buildNotificationIconWrapper() =>
Icons.notifications_none, NotificationIcon(count: unreadCount,onTap:onTap ,)
color: kcDarkGrey, ;
);
} }

View File

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

View File

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

View File

@ -1,6 +1,6 @@
name: yimaru_app name: yimaru_app
publish_to: 'none' publish_to: 'none'
version: 0.1.33+35 version: 0.1.41+43
description: A new Flutter project. description: A new Flutter project.
environment: environment:
@ -16,6 +16,7 @@ dependencies:
path: ^1.9.1 path: ^1.9.1
async: ^2.13.1 async: ^2.13.1
pinput: ^6.0.1 pinput: ^6.0.1
badges: ^3.2.0
stacked: ^3.4.0 stacked: ^3.4.0
iconsax: ^0.0.8 iconsax: ^0.0.8
chewie: ^1.13.0 chewie: ^1.13.0
@ -55,7 +56,6 @@ dependencies:
flutter_phone_direct_caller: ^2.2.1 flutter_phone_direct_caller: ^2.2.1
flutter_local_notifications: ^20.1.0 flutter_local_notifications: ^20.1.0
internet_connection_checker_plus: ^2.9.1+2 internet_connection_checker_plus: ^2.9.1+2
sign_in_with_apple: ^8.0.0
dev_dependencies: dev_dependencies:
flutter_test: 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/image_picker_service.dart';
import 'package:yimaru_app/services/google_auth_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/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/smart_auth_service.dart';
import 'package:yimaru_app/services/course_service.dart'; import 'package:yimaru_app/services/course_service.dart';
import 'package:yimaru_app/services/audio_player_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/learn_service.dart';
import 'package:yimaru_app/services/localization_service.dart'; import 'package:yimaru_app/services/localization_service.dart';
import 'package:yimaru_app/services/onboarding_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 // @stacked-import
@GenerateMocks( @GenerateMocks(
@ -57,6 +58,10 @@ import 'package:yimaru_app/services/onboarding_service.dart';
MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<LearnService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<LocalizationService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<OnboardingService>(onMissingStub: OnMissingStub.returnDefault), MockSpec<OnboardingService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<InAppNotificationService>(
onMissingStub: OnMissingStub.returnDefault),
MockSpec<PushNotificationService>(
onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec // @stacked-mock-spec
], ],
) )
@ -88,6 +93,8 @@ void registerServices() {
getAndRegisterLearnService(); getAndRegisterLearnService();
getAndRegisterLocalizationService(); getAndRegisterLocalizationService();
getAndRegisterOnboardingService(); getAndRegisterOnboardingService();
getAndRegisterInAppNotificationService();
getAndRegisterPushNotificationService();
// @stacked-mock-register // @stacked-mock-register
} }
@ -208,13 +215,6 @@ MockImageDownloaderService getAndRegisterImageDownloaderService() {
return service; return service;
} }
MockNotificationService getAndRegisterNotificationService() {
_removeRegistrationIfExists<NotificationService>();
final service = MockNotificationService();
locator.registerSingleton<NotificationService>(service);
return service;
}
MockSmartAuthService getAndRegisterSmartAuthService() { MockSmartAuthService getAndRegisterSmartAuthService() {
_removeRegistrationIfExists<SmartAuthService>(); _removeRegistrationIfExists<SmartAuthService>();
final service = MockSmartAuthService(); final service = MockSmartAuthService();
@ -291,6 +291,20 @@ MockOnboardingService getAndRegisterOnboardingService() {
locator.registerSingleton<OnboardingService>(service); locator.registerSingleton<OnboardingService>(service);
return 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 // @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() { 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'; import '../helpers/test_helpers.dart';
void main() { void main() {
group('NotificationServiceTest -', () { group('PushNotificationServiceTest -', () {
setUp(() => registerServices()); setUp(() => registerServices());
tearDown(() => locator.reset()); tearDown(() => locator.reset());
}); });

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