[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Better Experience for user login and register. (#27)

* Better Experience for users.

* Fixed issues from checker

* Update ataccount_dialog.dart

* Cleaner

authored by

C3B and committed by
GitHub
56b1c5ba 2ad3e019

+670 -376
assets/branding/gradient.webp

This is a binary file and will not be displayed.

+12
assets/images/ataccount.svg
··· 1 + <svg width="412" height="80" viewBox="0 0 412 80" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <path d="M230.46 29.4937C233.428 29.4937 235.973 30.0706 238.096 31.2243C240.219 32.3554 241.885 33.8259 243.095 35.6356C244.327 37.4454 245.069 39.3569 245.32 41.3703C245.339 41.5145 245.225 41.6418 245.079 41.6418H239.816C239.65 41.6418 239.506 41.5298 239.465 41.3703C239.032 39.6058 238.073 38.0562 236.589 36.7215C235.128 35.3868 233.085 34.7194 230.46 34.7194C228.406 34.7194 226.602 35.2624 225.05 36.3482C223.498 37.4114 222.288 38.9271 221.421 40.8952C220.553 42.8407 220.12 45.1482 220.12 47.8176V47.8515C220.12 50.5888 220.553 52.9302 221.421 54.8757C222.311 56.8212 223.532 58.3142 225.085 59.3548C226.66 60.3955 228.474 60.9158 230.529 60.9158C232.971 60.9158 234.946 60.305 236.452 59.0834C237.982 57.8392 238.997 56.233 239.5 54.2649C239.54 54.1053 239.684 53.9934 239.85 53.9934H245.181C245.309 53.9934 245.407 54.1056 245.389 54.2309C245.069 56.4705 244.236 58.4952 242.889 60.305C241.565 62.0921 239.842 63.5173 237.719 64.5805C235.619 65.6212 233.222 66.1415 230.529 66.1415C227.173 66.1415 224.251 65.3949 221.763 63.9019C219.298 62.3862 217.392 60.2597 216.045 57.5225C214.698 54.7626 214.025 51.5163 214.025 47.7837V47.7497C214.025 44.0397 214.71 40.8161 216.079 38.0788C217.449 35.3415 219.367 33.2264 221.832 31.7333C224.297 30.2403 227.173 29.4937 230.46 29.4937Z" fill="white"/> 3 + <path d="M265.59 29.4937C268.558 29.4937 271.103 30.0706 273.226 31.2243C275.349 32.3554 277.015 33.8259 278.225 35.6356C279.457 37.4454 280.199 39.3569 280.45 41.3703C280.468 41.5145 280.355 41.6418 280.209 41.6418H274.946C274.78 41.6418 274.636 41.5298 274.595 41.3703C274.162 39.6058 273.203 38.0562 271.719 36.7215C270.258 35.3868 268.215 34.7194 265.59 34.7194C263.536 34.7194 261.733 35.2624 260.18 36.3482C258.628 37.4114 257.418 38.9271 256.551 40.8952C255.684 42.8407 255.25 45.1482 255.25 47.8176V47.8515C255.25 50.5888 255.684 52.9302 256.551 54.8757C257.441 56.8212 258.662 58.3142 260.215 59.3548C261.79 60.3955 263.604 60.9158 265.659 60.9158C268.101 60.9158 270.076 60.305 271.582 59.0834C273.112 57.8392 274.127 56.233 274.63 54.2649C274.67 54.1053 274.814 53.9934 274.98 53.9934H280.311C280.439 53.9934 280.537 54.1056 280.519 54.2309C280.199 56.4705 279.366 58.4952 278.019 60.305C276.695 62.0921 274.972 63.5173 272.849 64.5805C270.749 65.6212 268.352 66.1415 265.659 66.1415C262.303 66.1415 259.381 65.3949 256.893 63.9019C254.428 62.3862 252.522 60.2597 251.175 57.5225C249.829 54.7626 249.155 51.5163 249.155 47.7837V47.7497C249.155 44.0397 249.84 40.8161 251.21 38.0788C252.579 35.3415 254.497 33.2264 256.962 31.7333C259.427 30.2403 262.303 29.4937 265.59 29.4937Z" fill="white"/> 4 + <path fill-rule="evenodd" clip-rule="evenodd" d="M300.754 29.4937C304.042 29.4937 306.918 30.2403 309.383 31.7333C311.871 33.2037 313.8 35.3076 315.169 38.0449C316.562 40.7595 317.258 43.9945 317.258 47.7497V47.8176C317.258 51.5729 316.562 54.8191 315.169 57.5564C313.8 60.2937 311.882 62.4088 309.417 63.9019C306.952 65.3949 304.076 66.1415 300.789 66.1415C297.502 66.1415 294.614 65.3949 292.126 63.9019C289.661 62.4088 287.732 60.2937 286.34 57.5564C284.97 54.7965 284.285 51.5502 284.285 47.8176V47.7497C284.285 43.9945 284.97 40.7595 286.34 38.0449C287.732 35.3076 289.661 33.2037 292.126 31.7333C294.614 30.2403 297.49 29.4937 300.754 29.4937ZM300.754 34.7194C298.7 34.7194 296.885 35.2397 295.31 36.2803C293.758 37.2983 292.548 38.7801 291.681 40.7256C290.814 42.6485 290.38 44.9898 290.38 47.7497V47.8176C290.38 50.5775 290.814 52.9415 291.681 54.9096C292.548 56.8551 293.758 58.3482 295.31 59.3888C296.885 60.4068 298.723 60.9158 300.823 60.9158C302.923 60.9158 304.738 60.4068 306.267 59.3888C307.819 58.3482 309.018 56.8551 309.862 54.9096C310.73 52.9415 311.163 50.5775 311.163 47.8176V47.7497C311.163 44.9898 310.73 42.6371 309.862 40.6916C309.018 38.7462 307.808 37.2644 306.233 36.2464C304.681 35.2284 302.855 34.7194 300.754 34.7194Z" fill="white"/> 5 + <path d="M329.071 52.0253C329.071 54.8983 329.744 57.1039 331.091 58.6422C332.46 60.1579 334.515 60.9158 337.254 60.9158C338.669 60.9158 339.936 60.6782 341.055 60.2032C342.196 59.7281 343.178 59.0608 343.999 58.2011C344.821 57.3189 345.449 56.2669 345.883 55.0453C346.316 53.8011 346.533 52.4325 346.533 50.9394V31.9098C346.533 30.9502 347.318 30.1724 348.286 30.1724H350.738C351.706 30.1724 352.491 30.9502 352.491 31.9098V63.7254C352.491 64.6849 351.706 65.4628 350.738 65.4628H348.286C347.318 65.4628 346.533 64.6849 346.533 63.7254V60.5269C346.533 60.4793 346.494 60.4407 346.446 60.4407C346.415 60.4407 346.387 60.457 346.371 60.4834C345.735 61.5521 344.91 62.5106 343.897 63.3589C342.892 64.2186 341.682 64.8972 340.267 65.3949C338.852 65.8926 337.243 66.1415 335.439 66.1415C332.837 66.1415 330.612 65.6098 328.763 64.5466C326.936 63.4834 325.533 61.979 324.551 60.0335C323.592 58.0654 323.113 55.7466 323.113 53.0772V31.9098C323.113 30.9502 323.898 30.1724 324.866 30.1724H327.318C328.286 30.1724 329.071 30.9502 329.071 31.9098V52.0253Z" fill="white"/> 6 + <path d="M405.22 29.3037C405.22 29.7835 405.612 30.1724 406.097 30.1724H410.246C411.215 30.1724 412 30.9502 412 31.9098V33.3214C412 34.2809 411.215 35.0588 410.246 35.0588H406.097C405.612 35.0588 405.22 35.4477 405.22 35.9274V56.4366C405.22 58.3142 405.631 59.5924 406.453 60.271C407.274 60.9271 408.484 61.2551 410.082 61.2551C410.447 61.2551 410.79 61.2438 411.109 61.2212C411.586 61.1581 412 61.5306 412 62.0075V64.4722C412 65.2901 411.384 65.9769 410.561 66.0397C409.991 66.1075 409.409 66.1415 408.815 66.1415C405.551 66.1415 403.109 65.3723 401.488 63.834C399.867 62.2957 399.057 59.8412 399.057 56.4705V35.9274C399.057 35.4477 398.664 35.0588 398.18 35.0588H395.605C394.637 35.0588 393.852 34.2809 393.852 33.3214V31.9098C393.852 30.9502 394.637 30.1724 395.605 30.1724H398.18C398.664 30.1724 399.057 29.7835 399.057 29.3037V22.3746C399.057 21.415 399.842 20.6372 400.81 20.6372H403.467C404.435 20.6372 405.22 21.415 405.22 22.3746V29.3037Z" fill="white"/> 7 + <path fill-rule="evenodd" clip-rule="evenodd" d="M210.819 63.1205C211.244 64.2554 210.397 65.4628 209.176 65.4628H206.465C205.722 65.4628 205.06 64.9989 204.812 64.3048L200.43 52.0274C200.306 51.6804 199.975 51.4484 199.603 51.4484H180.966C180.595 51.4484 180.264 51.6801 180.14 52.0269L175.748 64.3059C175.5 64.9994 174.838 65.4628 174.096 65.4628H171.386C170.164 65.4628 169.316 64.2546 169.743 63.1195L186.825 17.6288C187.08 16.9485 187.735 16.4974 188.468 16.4974H192.125C192.858 16.4974 193.514 16.949 193.769 17.6298L210.819 63.1205ZM182.619 45.0977C182.417 45.6631 182.84 46.2567 183.445 46.2567H197.131C197.736 46.2567 198.159 45.6636 197.957 45.0983L190.388 23.8909C190.375 23.8525 190.338 23.8269 190.297 23.8269C190.256 23.8269 190.219 23.8525 190.206 23.8908L182.619 45.0977Z" fill="white"/> 8 + <path d="M377.315 29.4937C381.218 29.4937 384.242 30.6701 386.388 33.0228C388.557 35.3528 389.641 38.5312 389.641 42.558V63.7254C389.641 64.6849 388.856 65.4628 387.888 65.4628H385.436C384.468 65.4628 383.683 64.6849 383.683 63.7254V43.6099C383.683 40.7369 382.998 38.5426 381.629 37.0269C380.282 35.4886 378.239 34.7194 375.5 34.7194C373.628 34.7194 371.996 35.1379 370.604 35.9749C369.211 36.7893 368.127 37.9431 367.351 39.4361C366.598 40.9292 366.221 42.6824 366.221 44.6958V63.7254C366.221 64.6849 365.436 65.4628 364.468 65.4628H362.016C361.048 65.4628 360.263 64.6849 360.263 63.7254V31.9098C360.263 30.9502 361.048 30.1724 362.016 30.1724H364.468C365.436 30.1724 366.221 30.9502 366.221 31.9098V35.1085C366.221 35.156 366.26 35.1945 366.308 35.1945C366.339 35.1945 366.367 35.178 366.383 35.1514C367.341 33.5195 368.725 32.1765 370.535 31.1225C372.361 30.0367 374.621 29.4937 377.315 29.4937Z" fill="white"/> 9 + <path fill-rule="evenodd" clip-rule="evenodd" d="M126.435 63.0387C126.769 63.9638 126.068 64.9347 125.066 64.9347H117.17C116.536 64.9347 115.976 64.5323 115.786 63.9407L112.491 53.7086C112.396 53.4127 112.116 53.2116 111.799 53.2116H95.1194C94.8028 53.2116 94.5226 53.4127 94.4274 53.7085L91.1317 63.9408C90.9411 64.5324 90.3809 64.9347 89.7475 64.9347H81.8523C80.8499 64.9347 80.1492 63.963 80.4845 63.0378L96.966 17.5502C97.1715 16.983 97.7193 16.6041 98.3338 16.6041H108.618C109.233 16.6041 109.781 16.9835 109.987 17.551L126.435 63.0387ZM97.3216 44.7187C97.1741 45.1768 97.5233 45.6433 98.0137 45.6433H108.904C109.394 45.6433 109.743 45.1768 109.596 44.7188L103.595 26.0799C103.576 26.022 103.521 25.9826 103.459 25.9826C103.397 25.9826 103.343 26.022 103.324 26.0799L97.3216 44.7187Z" fill="white"/> 10 + <path d="M159.5 23.5227C159.5 24.3078 158.85 24.9442 158.049 24.9442H145.702C145.301 24.9442 144.976 25.2624 144.976 25.655V63.5132C144.976 64.2982 144.326 64.9347 143.525 64.9347H135.935C135.133 64.9347 134.483 64.2982 134.483 63.5132V25.655C134.483 25.2624 134.159 24.9442 133.758 24.9442H121.447C120.645 24.9442 119.995 24.3078 119.995 23.5227V18.0256C119.995 17.2405 120.645 16.6041 121.447 16.6041H158.049C158.85 16.6041 159.5 17.2405 159.5 18.0256V23.5227Z" fill="white"/> 11 + <path fill-rule="evenodd" clip-rule="evenodd" d="M40.4613 0C46.221 0 51.5068 0.991168 56.3178 2.97408C61.1287 4.88858 65.2958 7.58979 68.8193 11.0769C72.4106 14.4957 75.1555 18.5296 77.0528 23.1792C79.0179 27.8287 80 32.9576 80 38.5645C79.9999 43.0771 79.3896 46.94 78.17 50.1536C76.9503 53.3672 75.1212 55.8288 72.6818 57.5382C70.2425 59.1792 67.1928 60 63.5337 60C60.8912 60 58.4516 59.5217 56.2156 58.5645C54.0473 57.5388 52.3533 56.2051 51.1336 54.5641C50.9367 54.2991 50.7575 54.0293 50.5924 53.7571C49.4405 55.3959 47.9617 56.7598 46.153 57.8462C43.3071 59.4871 40.0886 60.3081 36.4974 60.3081C32.9738 60.3081 29.8226 59.4873 27.0444 57.8462C24.2664 56.2053 22.098 53.9148 20.5395 50.9748C18.981 48.0346 18.2021 44.6494 18.2021 40.8203C18.2021 36.8547 18.9811 33.4017 20.5395 30.4616C22.098 27.5215 24.2663 25.2303 27.0444 23.5893C29.8226 21.9483 32.9738 21.1284 36.4974 21.1284C39.4789 21.1284 42.0878 21.7779 44.3239 23.0771C45.6812 23.8242 46.9002 24.8492 47.9829 26.1506V22.8258H59.8747V43.3842C59.8747 45.6406 60.2138 47.2822 60.8914 48.3079C61.5691 49.2651 62.7888 49.7434 64.5505 49.7434C66.5833 49.7434 68.0406 48.8543 68.9215 47.0765C69.8699 45.2987 70.3444 42.4953 70.3444 38.6665C70.3444 34.4273 69.599 30.5637 68.1082 27.0765C66.6853 23.5895 64.6521 20.5812 62.0095 18.0513C59.3668 15.453 56.2156 13.4698 52.5566 12.1023C48.9654 10.7349 44.9677 10.0515 40.5635 10.0515C36.159 10.0515 32.1264 10.8031 28.4674 12.3074C24.8084 13.7432 21.6579 15.8295 19.0153 18.5645C16.3727 21.2995 14.306 24.5472 12.8152 28.3079C11.3923 32.0685 10.6804 36.2392 10.6804 40.8203C10.6804 44.9913 11.4258 48.8548 12.9166 52.4103C14.4751 55.9659 16.6097 59.077 19.3201 61.7436C22.0306 64.3419 25.1818 66.3935 28.7731 67.8977C32.4321 69.4019 36.3625 70.1536 40.5635 70.1536C46.4586 70.1535 52.0485 68.4782 57.3337 65.1278L61.9082 73.7438C58.7912 75.7951 55.3689 77.3337 51.642 78.3594C47.983 79.4534 44.2559 80 40.4613 80C34.9051 80 29.7214 79.0087 24.9105 77.0259C20.0995 75.043 15.8982 72.2736 12.3069 68.718C8.71554 65.1625 5.90348 61.0256 3.87066 56.3077C1.83785 51.5898 0.821315 46.4617 0.821289 40.9233C0.821289 34.9746 1.80338 29.5041 3.76845 24.5126C5.7335 19.5212 8.47848 15.2139 12.002 11.59C15.5933 7.96609 19.7946 5.12834 24.6056 3.07706C29.4843 1.02584 34.7695 1.94472e-05 40.4613 0ZM39.3432 31.6921C37.7171 31.6921 36.2605 32.0685 34.9731 32.8205C33.7535 33.5726 32.7704 34.6322 32.0251 35.9996C31.3475 37.3671 31.0093 38.9742 31.0092 40.8203C31.0092 42.5981 31.3475 44.1714 32.0251 45.5389C32.7704 46.8379 33.7535 47.863 34.9731 48.6151C36.2605 49.3671 37.7171 49.7434 39.3432 49.7434C41.0372 49.7434 42.4945 49.3672 43.7142 48.6151C44.9337 47.863 45.8825 46.8378 46.56 45.5389C47.2376 44.1714 47.5768 42.5981 47.5768 40.8203C47.5768 38.9742 47.2041 37.3671 46.4587 35.9996C45.7811 34.6322 44.8316 33.5726 43.612 32.8205C42.4601 32.0685 41.0371 31.6921 39.3432 31.6921Z" fill="white"/> 12 + </svg>
+6
ios/Podfile.lock
··· 22 22 - sqflite_darwin (0.0.4): 23 23 - Flutter 24 24 - FlutterMacOS 25 + - url_launcher_ios (0.0.1): 26 + - Flutter 25 27 - video_editor_sdk (3.2.0): 26 28 - Flutter 27 29 - imgly_sdk (= 3.2.0) ··· 38 40 - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 39 41 - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 40 42 - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) 43 + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 41 44 - video_editor_sdk (from `.symlinks/plugins/video_editor_sdk/ios`) 42 45 - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) 43 46 ··· 63 66 :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 64 67 sqflite_darwin: 65 68 :path: ".symlinks/plugins/sqflite_darwin/darwin" 69 + url_launcher_ios: 70 + :path: ".symlinks/plugins/url_launcher_ios/ios" 66 71 video_editor_sdk: 67 72 :path: ".symlinks/plugins/video_editor_sdk/ios" 68 73 video_player_avfoundation: ··· 79 84 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 80 85 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 81 86 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 87 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d 82 88 video_editor_sdk: 19a9191daf907739131ec359a140212bad5f3d6a 83 89 video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b 84 90
+88 -61
lib/screens/auth_prompt_screen.dart
··· 27 27 onPressed: onClose, 28 28 icon: Icon(FluentIcons.dismiss_24_regular, color: AppTheme.getTextColor(context)), 29 29 ), 30 - backgroundColor: isDarkMode ? AppColors.darkBackground.withAlpha(242) : AppColors.background, 30 + backgroundColor: Colors.transparent, 31 31 elevation: 0, 32 32 ) 33 33 : null, 34 - body: SafeArea( 35 - child: Center( 36 - child: Padding( 37 - padding: const EdgeInsets.all(24.0), 38 - child: Column( 39 - mainAxisAlignment: MainAxisAlignment.center, 40 - children: [ 41 - SvgPicture.asset( 42 - isDarkMode ? 'assets/images/logo_dark_mode.svg' : 'assets/images/logo_light_mode.svg', 43 - height: 80, 44 - width: 80, 45 - ), 46 - const SizedBox(height: 24), 47 - Text( 48 - 'Welcome to Spark', 49 - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: AppTheme.getTextColor(context)), 50 - textAlign: TextAlign.center, 51 - ), 52 - const SizedBox(height: 16), 53 - Text( 54 - 'Add an account to create videos, connect with friends, and more', 55 - textAlign: TextAlign.center, 56 - style: TextStyle(color: AppTheme.getSecondaryTextColor(context), fontSize: 16), 57 - ), 58 - const SizedBox(height: 40), 59 - SizedBox( 60 - width: double.infinity, 61 - child: ElevatedButton( 62 - style: ElevatedButton.styleFrom( 63 - backgroundColor: AppColors.primary, 64 - padding: const EdgeInsets.symmetric(vertical: 16), 65 - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 34 + body: Stack( 35 + children: [ 36 + Positioned.fill( 37 + child: Image.asset( 38 + 'assets/branding/gradient.webp', 39 + fit: BoxFit.cover, 40 + ), 41 + ), 42 + SafeArea( 43 + child: Center( 44 + child: Padding( 45 + padding: const EdgeInsets.all(24.0), 46 + child: Column( 47 + mainAxisAlignment: MainAxisAlignment.center, 48 + children: [ 49 + SvgPicture.asset( 50 + isDarkMode ? 'assets/images/logo_dark_mode.svg' : 'assets/images/logo_dark_mode.svg', 51 + height: 140, 52 + width: 140, 53 + ), 54 + const SizedBox(height: 21), 55 + Text( 56 + 'Welcome to Spark', 57 + style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold, color: AppColors.white), 58 + textAlign: TextAlign.center, 59 + ), 60 + const SizedBox(height: 5), 61 + SizedBox( 62 + width: 340, 63 + child: Text( 64 + 'Add an account to create videos, connect with friends, and more.', 65 + textAlign: TextAlign.center, 66 + style: TextStyle( 67 + color: AppColors.white, 68 + fontSize: 20, 69 + height: 1.5, 70 + ), 71 + ), 72 + ), 73 + const SizedBox(height: 60), 74 + ElevatedButton( 75 + style: ElevatedButton.styleFrom( 76 + backgroundColor: AppColors.primary, 77 + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), 78 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 79 + minimumSize: const Size(320, 60), 80 + ), 81 + onPressed: () { 82 + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const LoginScreen())); 83 + }, 84 + child: Row( 85 + mainAxisSize: MainAxisSize.min, 86 + mainAxisAlignment: MainAxisAlignment.center, 87 + children: [ 88 + const Text( 89 + 'Login with ', 90 + style: TextStyle(fontWeight: FontWeight.normal, fontSize: 20, color: AppColors.white), 91 + ), 92 + SvgPicture.asset( 93 + 'assets/images/ataccount.svg', 94 + height: 22, 95 + width: 100, 96 + ), 97 + ], 98 + ), 66 99 ), 67 - onPressed: () { 68 - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const LoginScreen())); 69 - }, 70 - child: const Text('Login', style: TextStyle(fontWeight: FontWeight.bold, color: AppColors.white)), 71 - ), 72 - ), 73 - const SizedBox(height: 16), 74 - SizedBox( 75 - width: double.infinity, 76 - child: ElevatedButton( 77 - style: ElevatedButton.styleFrom( 78 - backgroundColor: AppColors.primary, 79 - padding: const EdgeInsets.symmetric(vertical: 16), 80 - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 100 + const SizedBox(height: 16), 101 + ElevatedButton( 102 + style: ElevatedButton.styleFrom( 103 + backgroundColor: AppColors.primary, 104 + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), 105 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 106 + minimumSize: const Size(320, 60), 107 + ), 108 + onPressed: () { 109 + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const RegisterScreen())); 110 + }, 111 + child: const Text('Register', style: TextStyle(color: AppColors.white, fontSize: 20, fontWeight: FontWeight.normal)), 81 112 ), 82 - onPressed: () { 83 - Navigator.of(context).push(MaterialPageRoute(builder: (context) => const RegisterScreen())); 84 - }, 85 - child: const Text('Register', style: TextStyle(color: AppColors.white, fontWeight: FontWeight.bold)), 86 - ), 113 + if (onClose != null) ...[ 114 + const SizedBox(height: 24), 115 + TextButton( 116 + onPressed: onClose, 117 + child: Text('Continue browsing', style: TextStyle(color: AppTheme.getSecondaryTextColor(context))), 118 + ), 119 + ], 120 + ], 87 121 ), 88 - if (onClose != null) ...[ 89 - const SizedBox(height: 24), 90 - TextButton( 91 - onPressed: onClose, 92 - child: Text('Continue browsing', style: TextStyle(color: AppTheme.getSecondaryTextColor(context))), 93 - ), 94 - ], 95 - ], 122 + ), 96 123 ), 97 124 ), 98 - ), 125 + ], 99 126 ), 100 127 ); 101 128 }
+170 -138
lib/screens/login_screen.dart
··· 8 8 import '../services/onboarding_service.dart'; 9 9 import '../utils/app_colors.dart'; 10 10 import '../utils/app_theme.dart'; 11 + import '../widgets/ataccount_dialog.dart'; 11 12 12 13 class UpperCaseTextFormatter extends TextInputFormatter { 13 14 @override ··· 88 89 89 90 return Scaffold( 90 91 backgroundColor: AppTheme.getBackgroundColor(context), 91 - body: SafeArea( 92 - child: Center( 93 - child: SingleChildScrollView( 94 - padding: const EdgeInsets.all(24.0), 95 - child: Form( 96 - key: _formKey, 97 - autovalidateMode: AutovalidateMode.onUserInteraction, 98 - child: Column( 99 - mainAxisAlignment: MainAxisAlignment.center, 100 - crossAxisAlignment: CrossAxisAlignment.stretch, 101 - children: [ 102 - Container( 103 - width: 100, 104 - height: 100, 105 - margin: const EdgeInsets.only(bottom: 40), 106 - decoration: BoxDecoration(borderRadius: BorderRadius.circular(20)), 107 - child: SvgPicture.asset( 108 - isDarkMode ? 'assets/images/logo_dark_mode.svg' : 'assets/images/logo_light_mode.svg', 109 - width: 100, 110 - height: 100, 111 - ), 112 - ), 113 - 114 - Text( 115 - 'Login to your account', 116 - style: TextStyle(color: AppTheme.getTextColor(context), fontSize: 24, fontWeight: FontWeight.bold), 117 - textAlign: TextAlign.center, 118 - ), 119 - const SizedBox(height: 30), 120 - 121 - AutofillGroup( 122 - child: Column( 123 - children: [ 124 - TextField( 125 - controller: _handleController, 126 - focusNode: _handleFocusNode, 127 - decoration: InputDecoration( 128 - hintText: 'Handle', 129 - prefixIcon: const Icon(FluentIcons.person_24_regular, color: AppColors.primary), 130 - filled: true, 131 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 132 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 133 - contentPadding: const EdgeInsets.all(16), 134 - ), 135 - style: TextStyle(color: AppTheme.getTextColor(context)), 136 - textInputAction: TextInputAction.next, 137 - keyboardType: TextInputType.emailAddress, 138 - autofillHints: const [AutofillHints.username, AutofillHints.email], 139 - onEditingComplete: () => _passwordFocusNode.requestFocus(), 92 + body: Stack( 93 + children: [ 94 + Positioned.fill( 95 + child: Image.asset( 96 + 'assets/branding/gradient.webp', 97 + fit: BoxFit.cover, 98 + ), 99 + ), 100 + SafeArea( 101 + child: Center( 102 + child: SingleChildScrollView( 103 + padding: const EdgeInsets.all(24.0), 104 + child: Form( 105 + key: _formKey, 106 + autovalidateMode: AutovalidateMode.onUserInteraction, 107 + child: Column( 108 + mainAxisAlignment: MainAxisAlignment.center, 109 + crossAxisAlignment: CrossAxisAlignment.stretch, 110 + children: [ 111 + SvgPicture.asset( 112 + isDarkMode ? 'assets/images/logo_dark_mode.svg' : 'assets/images/logo_dark_mode.svg', 113 + height: 140, 114 + width: 140, 115 + ), 116 + const SizedBox(height: 21), 117 + Text( 118 + 'Login to your account', 119 + style: TextStyle(color: AppColors.white, fontSize: 26, fontWeight: FontWeight.bold), 120 + textAlign: TextAlign.center, 121 + ), 122 + const SizedBox(height: 5), 123 + SizedBox( 124 + width: 340, 125 + child: Wrap( 126 + alignment: WrapAlignment.center, 127 + crossAxisAlignment: WrapCrossAlignment.center, 128 + children: [ 129 + Text( 130 + 'Login using your existing ', 131 + style: TextStyle( 132 + color: AppColors.white, 133 + fontSize: 20, 134 + height: 1.7, 135 + ), 136 + ), 137 + SvgPicture.asset( 138 + 'assets/images/ataccount.svg', 139 + height: 25, 140 + width: 100, 141 + ), 142 + const SizedBox(width: 4), 143 + const ATAccountInfoIcon(), 144 + ], 140 145 ), 141 - const SizedBox(height: 16), 146 + ), 147 + const SizedBox(height: 30), 142 148 143 - TextField( 144 - controller: _passwordController, 145 - focusNode: _passwordFocusNode, 146 - decoration: InputDecoration( 147 - hintText: 'Password', 148 - prefixIcon: const Icon(FluentIcons.lock_closed_24_regular, color: AppColors.primary), 149 - suffixIcon: IconButton( 150 - onPressed: () { 151 - setState(() { 152 - _obscurePassword = !_obscurePassword; 153 - }); 154 - }, 155 - icon: Icon( 156 - _obscurePassword ? FluentIcons.eye_24_regular : FluentIcons.eye_off_24_regular, 157 - color: AppColors.primary, 149 + AutofillGroup( 150 + child: Column( 151 + children: [ 152 + TextField( 153 + controller: _handleController, 154 + focusNode: _handleFocusNode, 155 + decoration: InputDecoration( 156 + hintText: 'Handle', 157 + hintStyle: TextStyle(color: AppColors.hintText), 158 + prefixIcon: const Icon(FluentIcons.person_24_regular, color: AppColors.primary), 159 + filled: true, 160 + fillColor: AppColors.white.withAlpha(255), 161 + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 162 + contentPadding: const EdgeInsets.all(16), 158 163 ), 164 + style: TextStyle(color: AppTheme.getTextColor(context)), 165 + textInputAction: TextInputAction.next, 166 + keyboardType: TextInputType.emailAddress, 167 + autofillHints: const [AutofillHints.username, AutofillHints.email], 168 + onEditingComplete: () => _passwordFocusNode.requestFocus(), 159 169 ), 160 - filled: true, 161 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 162 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 163 - contentPadding: const EdgeInsets.all(16), 164 - ), 165 - style: TextStyle(color: AppTheme.getTextColor(context)), 166 - obscureText: _obscurePassword, 167 - textInputAction: TextInputAction.done, 168 - keyboardType: TextInputType.visiblePassword, 169 - autofillHints: const [AutofillHints.password], 170 - onEditingComplete: () { 171 - if (_showAuthCodeField) { 172 - _authCodeFocusNode.requestFocus(); 173 - } else { 174 - TextInput.finishAutofillContext(); 175 - _login(); 176 - } 177 - }, 178 - ), 170 + const SizedBox(height: 16), 179 171 180 - if (_showAuthCodeField) ...[ 181 - const SizedBox(height: 16), 182 - TextField( 183 - controller: _authCodeController, 184 - focusNode: _authCodeFocusNode, 185 - decoration: InputDecoration( 186 - hintText: 'Enter code (e.g., ABCD1-ZXC45)', 187 - helperText: 'Enter the code from your email', 188 - prefixIcon: const Icon(FluentIcons.key_24_regular, color: AppColors.primary), 189 - filled: true, 190 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 191 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 192 - contentPadding: const EdgeInsets.all(16), 172 + TextField( 173 + controller: _passwordController, 174 + focusNode: _passwordFocusNode, 175 + decoration: InputDecoration( 176 + hintText: 'Password', 177 + hintStyle: TextStyle(color: AppColors.hintText), 178 + prefixIcon: const Icon(FluentIcons.lock_closed_24_regular, color: AppColors.primary), 179 + suffixIcon: IconButton( 180 + onPressed: () { 181 + setState(() { 182 + _obscurePassword = !_obscurePassword; 183 + }); 184 + }, 185 + icon: Icon( 186 + _obscurePassword ? FluentIcons.eye_24_regular : FluentIcons.eye_off_24_regular, 187 + color: AppColors.primary, 188 + ), 189 + ), 190 + filled: true, 191 + fillColor: AppColors.white.withAlpha(255), 192 + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 193 + contentPadding: const EdgeInsets.all(16), 194 + ), 195 + style: TextStyle(color: AppTheme.getTextColor(context)), 196 + obscureText: _obscurePassword, 197 + textInputAction: TextInputAction.done, 198 + keyboardType: TextInputType.visiblePassword, 199 + autofillHints: const [AutofillHints.password], 200 + onEditingComplete: () { 201 + if (_showAuthCodeField) { 202 + _authCodeFocusNode.requestFocus(); 203 + } else { 204 + TextInput.finishAutofillContext(); 205 + _login(); 206 + } 207 + }, 193 208 ), 194 - style: TextStyle(color: AppTheme.getTextColor(context)), 195 - textInputAction: TextInputAction.done, 196 - keyboardType: TextInputType.text, 197 - textCapitalization: TextCapitalization.characters, 198 - inputFormatters: [ 199 - FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9\-]')), 200 - UpperCaseTextFormatter(), 201 - ], 202 - onEditingComplete: () { 203 - TextInput.finishAutofillContext(); 204 - _login(); 205 - }, 206 - ), 207 - ], 208 - ], 209 - ), 210 - ), 211 - const SizedBox(height: 24), 212 209 213 - if (authService.error != null) 214 - Padding( 215 - padding: const EdgeInsets.only(bottom: 16), 216 - child: Text( 217 - authService.error!, 218 - style: const TextStyle(color: AppColors.error, fontSize: 14), 219 - textAlign: TextAlign.center, 210 + if (_showAuthCodeField) ...[ 211 + const SizedBox(height: 16), 212 + TextField( 213 + controller: _authCodeController, 214 + focusNode: _authCodeFocusNode, 215 + decoration: InputDecoration( 216 + hintText: 'Enter code (e.g., ABCD1-ZXC45)', 217 + helperText: 'Enter the code from your email', 218 + prefixIcon: const Icon(FluentIcons.key_24_regular, color: AppColors.white), 219 + filled: true, 220 + fillColor: AppColors.white.withAlpha(100), 221 + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 222 + contentPadding: const EdgeInsets.all(16), 223 + ), 224 + style: TextStyle(color: AppTheme.getTextColor(context)), 225 + textInputAction: TextInputAction.done, 226 + keyboardType: TextInputType.text, 227 + textCapitalization: TextCapitalization.characters, 228 + inputFormatters: [ 229 + FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9\-]')), 230 + UpperCaseTextFormatter(), 231 + ], 232 + onEditingComplete: () { 233 + TextInput.finishAutofillContext(); 234 + _login(); 235 + }, 236 + ), 237 + ], 238 + ], 239 + ), 220 240 ), 221 - ), 241 + const SizedBox(height: 24), 242 + 243 + if (authService.error != null) 244 + Padding( 245 + padding: const EdgeInsets.only(bottom: 16), 246 + child: Text( 247 + authService.error!, 248 + style: const TextStyle(color: AppColors.error, fontSize: 14), 249 + textAlign: TextAlign.center, 250 + ), 251 + ), 222 252 223 - ElevatedButton( 224 - onPressed: authService.isLoading ? null : _login, 225 - style: ElevatedButton.styleFrom( 226 - backgroundColor: AppColors.primary, 227 - padding: const EdgeInsets.symmetric(vertical: 16), 228 - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 229 - disabledBackgroundColor: AppColors.primary.withAlpha(128), 230 - ), 231 - child: 232 - authService.isLoading 253 + ElevatedButton( 254 + onPressed: authService.isLoading ? null : _login, 255 + style: ElevatedButton.styleFrom( 256 + backgroundColor: AppColors.primary, 257 + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), 258 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 259 + minimumSize: const Size(320, 60), 260 + disabledBackgroundColor: AppColors.primary.withAlpha(128), 261 + ), 262 + child: authService.isLoading 233 263 ? const CircularProgressIndicator(color: AppColors.white) 234 264 : Text( 235 - _showAuthCodeField ? 'Verify Code' : 'Login', 236 - style: const TextStyle(fontWeight: FontWeight.bold, color: AppColors.white), 237 - ), 265 + _showAuthCodeField ? 'Verify Code' : 'Login', 266 + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: AppColors.white), 267 + ), 268 + ), 269 + ], 238 270 ), 239 - ], 271 + ), 240 272 ), 241 273 ), 242 274 ), 243 - ), 275 + ], 244 276 ), 245 277 ); 246 278 }
+218 -177
lib/screens/register_screen.dart
··· 8 8 import '../services/onboarding_service.dart'; 9 9 import '../utils/app_colors.dart'; 10 10 import '../utils/app_theme.dart'; 11 + import '../widgets/ataccount_dialog.dart'; 11 12 12 13 class RegisterScreen extends StatefulWidget { 13 14 const RegisterScreen({super.key}); ··· 82 83 83 84 return Scaffold( 84 85 backgroundColor: AppTheme.getBackgroundColor(context), 85 - appBar: AppBar( 86 - backgroundColor: isDarkMode ? AppColors.darkBackground.withAlpha(242) : AppColors.background, 87 - elevation: 0, 88 - leading: IconButton( 89 - padding: EdgeInsets.zero, 90 - onPressed: () => Navigator.of(context).pop(), 91 - icon: Icon(FluentIcons.chevron_left_24_regular, color: AppTheme.getTextColor(context)), 92 - ), 93 - title: Text('Create Account', style: TextStyle(color: AppTheme.getTextColor(context), fontWeight: FontWeight.bold)), 94 - ), 95 - body: SafeArea( 96 - child: GestureDetector( 97 - onTap: () => FocusScope.of(context).unfocus(), 98 - child: SingleChildScrollView( 99 - child: Padding( 100 - padding: const EdgeInsets.all(24.0), 101 - child: Column( 102 - crossAxisAlignment: CrossAxisAlignment.start, 103 - children: [ 104 - Center( 105 - child: Container( 106 - width: 80, 107 - height: 80, 108 - margin: const EdgeInsets.only(bottom: 30, top: 10), 109 - child: SvgPicture.asset( 110 - isDarkMode ? 'assets/images/logo_dark_mode.svg' : 'assets/images/logo_light_mode.svg', 86 + body: Stack( 87 + children: [ 88 + Positioned.fill( 89 + child: Image.asset( 90 + 'assets/branding/gradient.webp', 91 + fit: BoxFit.cover, 92 + ), 93 + ), 94 + SafeArea( 95 + child: GestureDetector( 96 + onTap: () => FocusScope.of(context).unfocus(), 97 + child: SingleChildScrollView( 98 + child: Padding( 99 + padding: const EdgeInsets.all(24.0), 100 + child: Column( 101 + crossAxisAlignment: CrossAxisAlignment.start, 102 + children: [ 103 + 104 + Center( 105 + child: SvgPicture.asset( 106 + 'assets/images/logo_dark_mode.svg', 107 + height: 140, 108 + width: 140, 109 + ), 111 110 ), 112 - ), 113 - ), 111 + const SizedBox(height: 21), 112 + Center( 113 + child: Text( 114 + 'Create Account', 115 + style: TextStyle(color: AppColors.white, fontSize: 26, fontWeight: FontWeight.bold), 116 + textAlign: TextAlign.center, 117 + ), 118 + ), 119 + const SizedBox(height: 5), 120 + Center( 121 + child: SizedBox( 122 + width: 340, 123 + child: Wrap( 124 + alignment: WrapAlignment.center, 125 + crossAxisAlignment: WrapCrossAlignment.center, 126 + children: [ 127 + Text( 128 + 'Create your new ', 129 + style: TextStyle( 130 + color: AppColors.white, 131 + fontSize: 20, 132 + height: 1.7, 133 + ), 134 + ), 135 + SvgPicture.asset( 136 + 'assets/images/ataccount.svg', 137 + height: 25, 138 + width: 100, 139 + ), 140 + const SizedBox(width: 4), 141 + const ATAccountInfoIcon(), 142 + ], 143 + ), 144 + ), 145 + ), 146 + 147 + const SizedBox(height: 30), 114 148 115 - if (AppConfig.signupsDisabled) ...[ 116 - Container( 117 - padding: const EdgeInsets.all(16), 118 - decoration: BoxDecoration(color: AppColors.error.withAlpha(26), borderRadius: BorderRadius.circular(12)), 119 - child: Row( 120 - children: [ 121 - const Icon(FluentIcons.warning_24_regular, color: AppColors.error), 122 - const SizedBox(width: 8), 123 - const Expanded( 124 - child: Text( 125 - 'New account registration is currently disabled while we correct issues in our system. We will try to re-enable it as soon as possible.', 126 - style: TextStyle(color: AppColors.error), 127 - ), 149 + if (AppConfig.signupsDisabled) ...[ 150 + Container( 151 + padding: const EdgeInsets.all(16), 152 + decoration: BoxDecoration(color: AppColors.error.withAlpha(26), borderRadius: BorderRadius.circular(12)), 153 + child: Row( 154 + children: [ 155 + const Icon(FluentIcons.warning_24_regular, color: AppColors.error), 156 + const SizedBox(width: 8), 157 + const Expanded( 158 + child: Text( 159 + 'New account registration is currently disabled while we correct issues in our system. We will try to re-enable it as soon as possible.', 160 + style: TextStyle(color: AppColors.error), 161 + ), 162 + ), 163 + ], 128 164 ), 129 - ], 165 + ), 166 + const SizedBox(height: 24), 167 + ], 168 + _buildLabel('Email'), 169 + const SizedBox(height: 8), 170 + TextField( 171 + controller: _emailController, 172 + keyboardType: TextInputType.emailAddress, 173 + decoration: InputDecoration( 174 + hintText: 'Your email address', 175 + hintStyle: TextStyle(color: AppColors.hintText), 176 + filled: true, 177 + fillColor: AppColors.white.withAlpha(255), 178 + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 179 + prefixIcon: const Icon(FluentIcons.mail_24_regular, color: AppColors.primary), 180 + contentPadding: const EdgeInsets.all(16), 181 + ), 182 + style: TextStyle(color: AppTheme.getTextColor(context)), 183 + onChanged: (_) => setState(() {}), 130 184 ), 131 - ), 132 - const SizedBox(height: 24), 133 - ], 134 - _buildLabel('Email'), 135 - const SizedBox(height: 8), 136 - TextField( 137 - controller: _emailController, 138 - keyboardType: TextInputType.emailAddress, 139 - decoration: InputDecoration( 140 - hintText: 'Your email address', 141 - filled: true, 142 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 143 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 144 - prefixIcon: const Icon(FluentIcons.mail_24_regular, color: AppColors.primary), 145 - contentPadding: const EdgeInsets.all(16), 146 - ), 147 - style: TextStyle(color: AppTheme.getTextColor(context)), 148 - onChanged: (_) => setState(() {}), 149 - ), 150 185 151 - const SizedBox(height: 24), 186 + const SizedBox(height: 24), 152 187 153 - _buildLabel('Username'), 154 - const SizedBox(height: 8), 155 - Container( 156 - decoration: BoxDecoration( 157 - color: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 158 - borderRadius: BorderRadius.circular(12), 159 - ), 160 - child: Row( 161 - children: [ 162 - const Padding( 163 - padding: EdgeInsets.only(left: 16), 164 - child: Icon(FluentIcons.mention_24_regular, color: AppColors.primary), 188 + _buildLabel('Username'), 189 + const SizedBox(height: 8), 190 + Container( 191 + decoration: BoxDecoration( 192 + color: AppColors.white.withAlpha(255), 193 + borderRadius: BorderRadius.circular(16), 165 194 ), 166 - Expanded( 167 - child: TextField( 168 - controller: _handleController, 169 - decoration: InputDecoration( 170 - hintText: 'username', 171 - border: InputBorder.none, 172 - filled: true, 173 - fillColor: Colors.transparent, 174 - contentPadding: const EdgeInsets.all(16), 195 + child: Row( 196 + children: [ 197 + const Padding( 198 + padding: EdgeInsets.only(left: 16), 199 + child: Icon(FluentIcons.mention_24_regular, color: AppColors.primary), 200 + ), 201 + Expanded( 202 + child: TextField( 203 + controller: _handleController, 204 + decoration: InputDecoration( 205 + hintText: 'username', 206 + hintStyle: TextStyle(color: AppColors.hintText), 207 + border: InputBorder.none, 208 + filled: true, 209 + fillColor: Colors.transparent, 210 + contentPadding: const EdgeInsets.all(16), 211 + ), 212 + style: TextStyle(color: AppTheme.getTextColor(context)), 213 + onChanged: (_) => setState(() {}), 214 + ), 215 + ), 216 + Padding( 217 + padding: const EdgeInsets.only(right: 16), 218 + child: Text('.sprk.so', style: TextStyle(fontSize: 16, color: isDarkMode ? AppColors.white.withAlpha(100) : AppTheme.getSecondaryTextColor(context))), 175 219 ), 176 - style: TextStyle(color: AppTheme.getTextColor(context)), 177 - onChanged: (_) => setState(() {}), 178 - ), 220 + ], 179 221 ), 180 - Padding( 181 - padding: const EdgeInsets.only(right: 16), 182 - child: Text('.sprk.so', style: TextStyle(fontSize: 16, color: AppTheme.getSecondaryTextColor(context))), 183 - ), 184 - ], 185 - ), 186 - ), 222 + ), 187 223 188 - const SizedBox(height: 24), 224 + const SizedBox(height: 24), 189 225 190 - _buildLabel('Password'), 191 - const SizedBox(height: 8), 192 - TextField( 193 - controller: _passwordController, 194 - obscureText: !_isPasswordVisible, 195 - decoration: InputDecoration( 196 - hintText: 'Your password', 197 - filled: true, 198 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 199 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 200 - prefixIcon: const Icon(FluentIcons.key_24_regular, color: AppColors.primary), 201 - suffixIcon: IconButton( 202 - onPressed: () { 203 - setState(() { 204 - _isPasswordVisible = !_isPasswordVisible; 205 - }); 206 - }, 207 - icon: Icon( 208 - _isPasswordVisible ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular, 209 - color: AppColors.primary, 226 + _buildLabel('Password'), 227 + const SizedBox(height: 8), 228 + TextField( 229 + controller: _passwordController, 230 + obscureText: !_isPasswordVisible, 231 + decoration: InputDecoration( 232 + hintText: 'Your password', 233 + hintStyle: TextStyle(color: AppColors.hintText), 234 + filled: true, 235 + fillColor: AppColors.white.withAlpha(255), 236 + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 237 + prefixIcon: const Icon(FluentIcons.key_24_regular, color: AppColors.primary), 238 + suffixIcon: IconButton( 239 + onPressed: () { 240 + setState(() { 241 + _isPasswordVisible = !_isPasswordVisible; 242 + }); 243 + }, 244 + icon: Icon( 245 + _isPasswordVisible ? FluentIcons.eye_off_24_regular : FluentIcons.eye_24_regular, 246 + color: AppColors.primary, 247 + ), 248 + ), 249 + contentPadding: const EdgeInsets.all(16), 210 250 ), 251 + style: TextStyle(color: AppTheme.getTextColor(context)), 252 + onChanged: (_) => setState(() {}), 211 253 ), 212 - contentPadding: const EdgeInsets.all(16), 213 - ), 214 - style: TextStyle(color: AppTheme.getTextColor(context)), 215 - onChanged: (_) => setState(() {}), 216 - ), 217 254 218 - const SizedBox(height: 24), 255 + const SizedBox(height: 14), 219 256 220 - _buildLabel('Invite Code (Optional)'), 221 - const SizedBox(height: 8), 222 - TextField( 223 - controller: _inviteCodeController, 224 - decoration: InputDecoration( 225 - hintText: 'Enter invite code if you have one', 226 - filled: true, 227 - fillColor: isDarkMode ? AppColors.deepPurple : Colors.grey[200], 228 - border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), 229 - prefixIcon: const Icon(FluentIcons.tag_24_regular, color: AppColors.primary), 230 - contentPadding: const EdgeInsets.all(16), 231 - ), 232 - style: TextStyle(color: AppTheme.getTextColor(context)), 233 - ), 257 + // _buildLabel('Invite Code (Optional)'), 258 + // const SizedBox(height: 8), 259 + // TextField( 260 + // controller: _inviteCodeController, 261 + // decoration: InputDecoration( 262 + // hintText: 'Enter invite code if you have one', 263 + // hintStyle: TextStyle(color: AppColors.hintText), 264 + // filled: true, 265 + // fillColor: AppColors.white.withAlpha(255), 266 + // border: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), 267 + // prefixIcon: const Icon(FluentIcons.tag_24_regular, color: AppColors.primary), 268 + // contentPadding: const EdgeInsets.all(16), 269 + // ), 270 + // style: TextStyle(color: AppTheme.getTextColor(context)), 271 + // ), 234 272 235 - if (_errorMessage != null) ...[ 236 - const SizedBox(height: 24), 237 - Container( 238 - padding: const EdgeInsets.all(16), 239 - decoration: BoxDecoration(color: AppColors.error.withAlpha(26), borderRadius: BorderRadius.circular(12)), 240 - child: Row( 241 - children: [ 242 - const Icon(FluentIcons.warning_24_regular, color: AppColors.error), 243 - const SizedBox(width: 8), 244 - Expanded(child: Text(_errorMessage!, style: const TextStyle(color: AppColors.error))), 245 - ], 246 - ), 247 - ), 248 - ], 273 + if (_errorMessage != null) ...[ 274 + const SizedBox(height: 24), 275 + Container( 276 + padding: const EdgeInsets.all(16), 277 + decoration: BoxDecoration(color: AppColors.error.withAlpha(26), borderRadius: BorderRadius.circular(12)), 278 + child: Row( 279 + children: [ 280 + const Icon(FluentIcons.warning_24_regular, color: AppColors.error), 281 + const SizedBox(width: 8), 282 + Expanded(child: Text(_errorMessage!, style: const TextStyle(color: AppColors.error))), 283 + ], 284 + ), 285 + ), 286 + ], 249 287 250 - const SizedBox(height: 40), 288 + const SizedBox(height: 40), 251 289 252 - SizedBox( 253 - width: double.infinity, 254 - child: ElevatedButton( 255 - style: ElevatedButton.styleFrom( 256 - backgroundColor: AppColors.primary, 257 - padding: const EdgeInsets.symmetric(vertical: 16), 258 - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), 290 + SizedBox( 291 + width: double.infinity, 292 + child: ElevatedButton( 293 + style: ElevatedButton.styleFrom( 294 + backgroundColor: AppColors.primary, 295 + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24), 296 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 297 + minimumSize: const Size(0, 60), 298 + ), 299 + onPressed: _isFormValid() && !_isRegistering ? _register : null, 300 + child: 301 + _isRegistering 302 + ? const CircularProgressIndicator(color: AppColors.white) 303 + : const Text( 304 + 'Create Account', 305 + style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: AppColors.white), 306 + ), 307 + ), 259 308 ), 260 - onPressed: _isFormValid() && !_isRegistering ? _register : null, 261 - child: 262 - _isRegistering 263 - ? const CircularProgressIndicator(color: AppColors.white) 264 - : const Text( 265 - 'Create Account', 266 - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.white), 267 - ), 268 - ), 269 - ), 270 309 271 - const SizedBox(height: 24), 310 + const SizedBox(height: 24), 272 311 273 - Row( 274 - mainAxisAlignment: MainAxisAlignment.center, 275 - children: [ 276 - Text('Already have an account?', style: TextStyle(color: AppTheme.getSecondaryTextColor(context))), 277 - TextButton( 278 - onPressed: () => Navigator.of(context).pop(), 279 - child: const Text('Sign in', style: TextStyle(color: AppColors.primary, fontWeight: FontWeight.bold)), 312 + Row( 313 + mainAxisAlignment: MainAxisAlignment.center, 314 + children: [ 315 + Text('Already have an account?', style: TextStyle(color: AppColors.white)), 316 + TextButton( 317 + onPressed: () => Navigator.of(context).pop(), 318 + child: const Text('Sign in', style: TextStyle(color: AppColors.white, fontWeight: FontWeight.bold)), 319 + ), 320 + ], 280 321 ), 281 322 ], 282 323 ), 283 - ], 324 + ), 284 325 ), 285 326 ), 286 327 ), 287 - ), 328 + ], 288 329 ), 289 330 ); 290 331 } 291 332 292 333 Widget _buildLabel(String text) { 293 - return Text(text, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppTheme.getTextColor(context))); 334 + return Text(text, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.white)); 294 335 } 295 336 }
+2
lib/utils/app_colors.dart
··· 17 17 static const Color green = Color(0xFF12DB59); // For comments 18 18 static const Color red = Color(0xFFFF3A2C); // For likes 19 19 static const Color orange = Color(0xFFFF7B00); // For alerts and danger 20 + static const Color hintColor = Color(0xFF808080); 20 21 21 22 static const Color primary = pink; 22 23 static const Color secondary = brightPurple; ··· 32 33 static const Color textSecondary = darkPurple; 33 34 static const Color textLight = lightLavender; 34 35 static const Color textOnDark = white; 36 + static const Color hintText = hintColor; 35 37 36 38 static const Color border = darkPurple; 37 39 static const Color divider = lightLavender;
+98
lib/widgets/ataccount_dialog.dart
··· 1 + import 'package:flutter/material.dart'; 2 + import 'package:flutter_svg/flutter_svg.dart'; 3 + import 'package:url_launcher/url_launcher.dart'; 4 + 5 + import '../utils/app_colors.dart'; 6 + 7 + class ATAccountInfoIcon extends StatelessWidget { 8 + const ATAccountInfoIcon({super.key}); 9 + 10 + @override 11 + Widget build(BuildContext context) { 12 + return InkWell( 13 + onTap: () => _showATAccountDialog(context), 14 + child: Container( 15 + width: 18, 16 + height: 18, 17 + margin: const EdgeInsets.only(top: 4, left: 4), 18 + decoration: const BoxDecoration( 19 + color: Colors.white, 20 + shape: BoxShape.circle, 21 + ), 22 + child: const Center( 23 + child: Icon(Icons.question_mark, size: 14, color: AppColors.primary), 24 + ), 25 + ), 26 + ); 27 + } 28 + 29 + void _showATAccountDialog(BuildContext context) { 30 + showDialog( 31 + context: context, 32 + builder: (context) => AlertDialog( 33 + backgroundColor: AppColors.deepPurple, 34 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 35 + titlePadding: const EdgeInsets.only(top: 32, left: 24, right: 24, bottom: 0), 36 + contentPadding: const EdgeInsets.fromLTRB(24, 12, 24, 0), 37 + title: Column( 38 + children: [ 39 + SvgPicture.asset( 40 + 'assets/images/ataccount.svg', 41 + height: 40, 42 + ), 43 + const SizedBox(height: 18), 44 + Text( 45 + 'What is an AT Account?', 46 + style: TextStyle( 47 + color: AppColors.lightLavender, 48 + fontWeight: FontWeight.bold, 49 + fontSize: 20, 50 + ), 51 + textAlign: TextAlign.center, 52 + ), 53 + ], 54 + ), 55 + content: const Padding( 56 + padding: EdgeInsets.only(top: 8, bottom: 8), 57 + child: Text( 58 + 'An ATAccount is your identity on the decentralized AT Protocol.\n\nUse it across Spark, Bluesky, and other ATmosphere apps with just one login.\n\nIt keeps your data safe, gives you control over your content, and ensures a seamless experience across platforms.', 59 + style: TextStyle(color: AppColors.lightLavender, fontSize: 16, height: 1.6), 60 + textAlign: TextAlign.center, 61 + ), 62 + ), 63 + actionsAlignment: MainAxisAlignment.center, 64 + actions: [ 65 + Row( 66 + children: [ 67 + Expanded( 68 + child: TextButton( 69 + onPressed: () async { 70 + Navigator.of(context).pop(); 71 + await launchUrl(Uri.parse('https://atproto.com/specs/account')); 72 + }, 73 + child: Text( 74 + 'Learn more', 75 + style: TextStyle(color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 16), 76 + ), 77 + ), 78 + ), 79 + const SizedBox(width: 12), 80 + Expanded( 81 + child: TextButton( 82 + style: TextButton.styleFrom( 83 + backgroundColor: AppColors.primary, 84 + foregroundColor: AppColors.white, 85 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), 86 + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), 87 + ), 88 + onPressed: () => Navigator.of(context).pop(), 89 + child: const Text('Got it', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), 90 + ), 91 + ), 92 + ], 93 + ), 94 + ], 95 + ), 96 + ); 97 + } 98 + }
+4
linux/flutter/generated_plugin_registrant.cc
··· 8 8 9 9 #include <file_selector_linux/file_selector_plugin.h> 10 10 #include <fvp/fvp_plugin.h> 11 + #include <url_launcher_linux/url_launcher_plugin.h> 11 12 12 13 void fl_register_plugins(FlPluginRegistry* registry) { 13 14 g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = ··· 16 17 g_autoptr(FlPluginRegistrar) fvp_registrar = 17 18 fl_plugin_registry_get_registrar_for_plugin(registry, "FvpPlugin"); 18 19 fvp_plugin_register_with_registrar(fvp_registrar); 20 + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 23 }
+1
linux/flutter/generated_plugins.cmake
··· 5 5 list(APPEND FLUTTER_PLUGIN_LIST 6 6 file_selector_linux 7 7 fvp 8 + url_launcher_linux 8 9 ) 9 10 10 11 list(APPEND FLUTTER_FFI_PLUGIN_LIST
+2
macos/Flutter/GeneratedPluginRegistrant.swift
··· 10 10 import path_provider_foundation 11 11 import shared_preferences_foundation 12 12 import sqflite_darwin 13 + import url_launcher_macos 13 14 import video_player_avfoundation 14 15 15 16 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ··· 18 19 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 19 20 SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 20 21 SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 22 + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 21 23 FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 22 24 }
+64
pubspec.lock
··· 1005 1005 url: "https://pub.dev" 1006 1006 source: hosted 1007 1007 version: "2.2.2" 1008 + url_launcher: 1009 + dependency: "direct main" 1010 + description: 1011 + name: url_launcher 1012 + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 1013 + url: "https://pub.dev" 1014 + source: hosted 1015 + version: "6.3.1" 1016 + url_launcher_android: 1017 + dependency: transitive 1018 + description: 1019 + name: url_launcher_android 1020 + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" 1021 + url: "https://pub.dev" 1022 + source: hosted 1023 + version: "6.3.16" 1024 + url_launcher_ios: 1025 + dependency: transitive 1026 + description: 1027 + name: url_launcher_ios 1028 + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" 1029 + url: "https://pub.dev" 1030 + source: hosted 1031 + version: "6.3.3" 1032 + url_launcher_linux: 1033 + dependency: transitive 1034 + description: 1035 + name: url_launcher_linux 1036 + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 1037 + url: "https://pub.dev" 1038 + source: hosted 1039 + version: "3.2.1" 1040 + url_launcher_macos: 1041 + dependency: transitive 1042 + description: 1043 + name: url_launcher_macos 1044 + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 1045 + url: "https://pub.dev" 1046 + source: hosted 1047 + version: "3.2.2" 1048 + url_launcher_platform_interface: 1049 + dependency: transitive 1050 + description: 1051 + name: url_launcher_platform_interface 1052 + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 1053 + url: "https://pub.dev" 1054 + source: hosted 1055 + version: "2.3.2" 1056 + url_launcher_web: 1057 + dependency: transitive 1058 + description: 1059 + name: url_launcher_web 1060 + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" 1061 + url: "https://pub.dev" 1062 + source: hosted 1063 + version: "2.4.1" 1064 + url_launcher_windows: 1065 + dependency: transitive 1066 + description: 1067 + name: url_launcher_windows 1068 + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 1069 + url: "https://pub.dev" 1070 + source: hosted 1071 + version: "3.1.4" 1008 1072 uuid: 1009 1073 dependency: transitive 1010 1074 description:
+1
pubspec.yaml
··· 23 23 atproto: ^0.13.3 24 24 bluesky: ^0.18.10 25 25 http: ^1.2.0 26 + url_launcher: ^6.2.5 26 27 dio: ^5.4.0 27 28 shared_preferences: ^2.5.3 28 29 visibility_detector: ^0.4.0+2
+3
windows/flutter/generated_plugin_registrant.cc
··· 8 8 9 9 #include <file_selector_windows/file_selector_windows.h> 10 10 #include <fvp/fvp_plugin_c_api.h> 11 + #include <url_launcher_windows/url_launcher_windows.h> 11 12 12 13 void RegisterPlugins(flutter::PluginRegistry* registry) { 13 14 FileSelectorWindowsRegisterWithRegistrar( 14 15 registry->GetRegistrarForPlugin("FileSelectorWindows")); 15 16 FvpPluginCApiRegisterWithRegistrar( 16 17 registry->GetRegistrarForPlugin("FvpPluginCApi")); 18 + UrlLauncherWindowsRegisterWithRegistrar( 19 + registry->GetRegistrarForPlugin("UrlLauncherWindows")); 17 20 }
+1
windows/flutter/generated_plugins.cmake
··· 5 5 list(APPEND FLUTTER_PLUGIN_LIST 6 6 file_selector_windows 7 7 fvp 8 + url_launcher_windows 8 9 ) 9 10 10 11 list(APPEND FLUTTER_FFI_PLUGIN_LIST